diff --git a/README.md b/README.md index 0adbb76..99af226 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Slides for testing in software - https://docs.google.com/presentation/d/1vtVaw38 - by Hans Jakob Damsgaard - Implementation of interface and transaction specifications for the AMBA AXI4 protocol in Scala to make testing of compliant Chisel components easier. - found in `./axi4/` +- UPDATE: moved to [chiselverify](https://github.com/chiselverify/chiselverify) ### Assertions with time - by Niels Frederik Frandsen and Victor Alexander Hansen diff --git a/axi4/.gitignore b/axi4/.gitignore deleted file mode 100644 index 7a2ef20..0000000 --- a/axi4/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# Ignore metals-related temp files -.bloop/ -.metals/ -.vscode/ -project/.bloop/ -project/project/ -project/src/ -project/target/ -project/metals.sbt - -# Ignore generated files -target/ -test_run_dir/ - -# Ignore IP files -src/main/resources/ \ No newline at end of file diff --git a/axi4/.scalafmt.conf b/axi4/.scalafmt.conf deleted file mode 100644 index aba989f..0000000 --- a/axi4/.scalafmt.conf +++ /dev/null @@ -1,7 +0,0 @@ -version = 2.6.4 - -maxColumn = 120 -align = most -continuationIndent.defnSite = 2 -assumeStandardLibraryStripMargin = true -docstrings = ScalaDoc diff --git a/axi4/README.md b/axi4/README.md deleted file mode 100644 index 86c4092..0000000 --- a/axi4/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# Verification of AMBA AXI Interfaced Components -This project focuses on implementation of AXI4 master interface definitions and a small framework providing support for all of the defined transactions from the AXI master's point of view in the protocol. - -### Extras -- Functional AXI slave (like above) - - Concurrent interfaces -- AXI Lite interface -- AXI Stream interface (see [here](https://developer.arm.com/documentation/ihi0051/latest/)) - -## Documentation -The AXI protocol defines five independent channels for reads and writes as listed below. As they are independent, read/write addresses may be transferred to a slave ahead of transferring the data. So-called _transactions_ consist of one or more _tranfers_ across a set of channels. Data is transferred in _bursts_ which consist of one or more _beats_. The protocol also supports multiple outstanding transactions and out-of-order completion by using tagged packets. All channels use simple decoupled ready-valid signalling. - -### Channels -The following channels are defined in the AXI4 protocol. ID fields are partly optional in that they do not have to be unique as the interconnect must append master numbers to them to ensure correct operation. -- _Write address channel_ used to initiate a write transaction from master to slave. A transfer contains ID, address, burst length, burst type, cacheability etc. (see page A2-29) - - `AWID [_:0]` ID field - - `AWADDR [_:0]` start address of a write transaction - - `AWLEN [7:0]` number of beats in the burst - - `AWSIZE [2:0]` beat size encoding, i.e. number of bytes per beat is 2^AWSIZE - - `AWBURST [1:0]` one of _FIXED_, _INCR_, or _WRAP_ - - `AWLOCK`, `AWCACHE [3:0]`, `AWPROT [2:0]`, and `AWQOS [3:0]` control data protection and quality of service - - _Optional_ `AWREGION [3:0]` allows sharing of a single physical interface for multiple logical regions - - _Optional_ `AWUSER [_:0]` - - `AWREADY` and `AWVALID` handshake signals - -- _Write data channel_ used to transfer write data from master to slave. A transfer contains ID, data, byte-wide write enable etc. (see page A2-30) - - `WDATA [_:0]` write data - - `WSTRB [_:0]` write strobe (i.e. one bit per byte of data) - - `WLAST` indicates whether this is the last beat in a burst - - _Optional_ `WUSER [_:0]` - - `WREADY` and `WVALID` handshake signals - -- _Write response channel_ used to inform a master about the completion of a write transaction. A transfer contains ID, write status etc. (see page A2-31) - - `BID [_:0]` ID field - - `BRESP [1:0]` write response from the slave; i.e. one of _OKAY_, _EXOKAY_, _SLVERR_, or _DECERR_ - - _Optional_ `BUSER` - - `BREADY` and `BVALID` handshake signals - -- _Read address channel_ used to initiate a read transaction from slave to master. A transfer contains ID, address, burst length, burst type, cacheability etc. (see page A2-32) - - `ARID [_:0]` ID field - - `ARADDR [_:0]` start address of a read transaction - - `ARLEN [7:0]` number of beats in the burst - - `ARSIZE [2:0]` beat size encoding - - `ARBURST [1:0]` one of _FIXED_, _INCR_, or _WRAP_ - - `ARLOCK`, `ARCACHE [3:0]`, `ARPROT [2:0]`, and `ARQOS [3:0]` control data protection and quality of service - - _Optional_ `ARREGION [3:0]` allows sharing of a single physical interface for multiple logical regions - - _Optional_ `ARUSER [_:0]` - - `ARREADY` and `ARVALID` handshake signals - -- _Read data channel_ used to transfer read data from slave to master. A transfer contains ID, data etc. (see page A2-33) - - `RID [_:0]` ID field - - `RDATA [_:0]` read data - - `RRESP [1:0]` read response from the slave - - `RLAST` indicates whether this is the last beat in a burst - - _Optional_ `RUSER [_:0]` - - `RREADY` and `RVALID` handshake signals - -Additionally, two global signals are used (see page A2-28) -- `ACLK` a shared global clock signal -- `ARESETn` a shared global active-low reset signal - -Channel descriptions are available in `./src/main/scala/axi4/Defs.scala`. DUVs must conform to the signal names and interfaces provided to function correctly - hence, their IO should extend either the available master or slave interfaces. To enable this more easily, an AXI master can simply extend the Master class found in `./src/main/scala/axi4/Master.scala`, and vice-versa for AXI slaves for which the relevant class is found in `./src/main/scala/axi4/Slave.scala`. - -An example of a black-boxed block-RAM with AXI interface is provided in `./src/main/scala/VivadoAXIMemory.scala` - note, however, that due to copyright of the IP source code, you will need to generate the appropriate IP block within Xilinx Vivado to use the blackbox. A basic tester is provided in `./src/test/scala/VivadoAXIMemoryTester.scala`. - -### References -The full public protocol specification is available from ARM [here](https://developer.arm.com/documentation/ihi0022/e/) and in PDF format [here](http://www.gstitt.ece.ufl.edu/courses/fall15/eel4720_5721/labs/refs/AXI4_specification.pdf). A good video introduction is available from [ARM's YouTube channel](https://www.youtube.com/watch?v=7Vl9JrGgNwk). - -## Notes -The following are taken from the specification. - -### Regarding ready-valid interface -- No combinational logic between `Ready` and `Valid` in neither master nor slave components -- A source _may not_ wait until `Ready` is asserted before asserting `Valid` -- A destination _may_ wait until `Valid` is asserted before asserting `Ready` -- Once asserted, `Valid` _must_ remain asserted until a handshake occurs -- If `Ready` is asserted, it _can_ be deasserted before assertion of `Valid` -- Transfers occur at the first rising edge after both `Ready` and `Valid` are asserted -- Address channels _should_ per default have `AWREADY` and `ARREADY` asserted to avoid transfers taking at least two cycles - -### Regarding channel relationships -- A write response must always follow the last write transfer in a write transaction -- Read data must always follow the address to which it the data relates -- Write data _can_ appear before or simultaneously with the write address for the transaction - -### Regarding transactions -- Bursts _must not_ cross a 4KB boundary (to avoid device address space mapping issues) -- Burst length is in \[1, 256\] for burst type INCR; for all other burst types it is in \[1, 16\] -- Bursts _must_ be completed (i.e. no support for early termination) -- Beat size _must not_ exceed the data bus width of neither the master nor the slave. -- Narrow transfers (i.e. transfers using a subsection of the data bus) are implemented using `WSTRB` -- Byte invariance means that some transfers can be little endian while others can be big endian -- Unaligned transfers are indicated either with the low-order address bits or using `WSTRB` -- Only one response is returned for write transactions, whereas a different response may be returned for each beat in a read transaction - and the entire transaction _must_ be completed regardless of potential errors -- Asserting `AxCACHE[1]` means that a transaction is _modifiable_ and can, for example, be broken down into multiple transactions, combined with other transactions, or fetch more data than requested -- Transaction ordering must be preserved for non-modifiable transactions with the same ID to the same slave (see pages A4-63 to A4-64) - -### Regarding multiple transactions -- The ID signals can be used to identify separate transactions that must be returned in order -- AXI4 does _not_ support write data interleaving diff --git a/axi4/build.sbt b/axi4/build.sbt deleted file mode 100644 index 55c601b..0000000 --- a/axi4/build.sbt +++ /dev/null @@ -1,14 +0,0 @@ -scalaVersion := "2.12.12" - -scalacOptions := Seq("-Xsource:2.11") - -resolvers ++= Seq( - Resolver.sonatypeRepo("snapshots"), - Resolver.sonatypeRepo("releases") -) - -libraryDependencies += "edu.berkeley.cs" %% "chisel3" % "3.1.6" -libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" -libraryDependencies += "edu.berkeley.cs" %% "chisel-iotesters" % "1.4.2" -libraryDependencies += "edu.berkeley.cs" %% "chiseltest" % "0.2.2" - diff --git a/axi4/project/build.properties b/axi4/project/build.properties deleted file mode 100644 index 5a9ed92..0000000 --- a/axi4/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -sbt.version=1.3.4 diff --git a/axi4/src/main/scala/VivadoAXIMemory.scala b/axi4/src/main/scala/VivadoAXIMemory.scala deleted file mode 100644 index 9b65a03..0000000 --- a/axi4/src/main/scala/VivadoAXIMemory.scala +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Author: Hans Jakob Damsgaard, hansjakobdamsgaard@gmail.com - * - * Purpose: Implementation of a testing framework for AXI4-compliant devices. - * - * Content: A blackbox for an AXI4 block RAM generated by Vivado. -*/ - -import axi4._ -import chisel3._ -import chisel3.util._ - -/** Vivado IP BRAM with full AXI4 interface */ -class mymem extends BlackBox with HasBlackBoxResource { - val io = IO(new Bundle { - /** Address write */ - val s00_axi_awid = Input(UInt(1.W)) - val s00_axi_awaddr = Input(UInt(10.W)) - val s00_axi_awlen = Input(UInt(8.W)) - val s00_axi_awsize = Input(UInt(3.W)) - val s00_axi_awburst = Input(UInt(2.W)) - val s00_axi_awlock = Input(Bool()) - val s00_axi_awcache = Input(UInt(4.W)) - val s00_axi_awprot = Input(UInt(3.W)) - val s00_axi_awregion = Input(UInt(4.W)) - val s00_axi_awqos = Input(UInt(4.W)) - val s00_axi_awvalid = Input(Bool()) - val s00_axi_awready = Output(Bool()) - - /** Data write */ - val s00_axi_wdata = Input(UInt(32.W)) - val s00_axi_wstrb = Input(UInt(4.W)) - val s00_axi_wlast = Input(Bool()) - val s00_axi_wvalid = Input(Bool()) - val s00_axi_wready = Output(Bool()) - - /** Write response */ - val s00_axi_bid = Output(UInt(1.W)) - val s00_axi_bresp = Output(UInt(2.W)) - val s00_axi_bvalid = Output(Bool()) - val s00_axi_bready = Input(Bool()) - - /** Address read */ - val s00_axi_arid = Input(UInt(1.W)) - val s00_axi_araddr = Input(UInt(10.W)) - val s00_axi_arlen = Input(UInt(8.W)) - val s00_axi_arsize = Input(UInt(3.W)) - val s00_axi_arburst = Input(UInt(2.W)) - val s00_axi_arlock = Input(Bool()) - val s00_axi_arcache = Input(UInt(4.W)) - val s00_axi_arprot = Input(UInt(3.W)) - val s00_axi_arregion = Input(UInt(4.W)) - val s00_axi_arqos = Input(UInt(4.W)) - val s00_axi_arvalid = Input(Bool()) - val s00_axi_arready = Output(Bool()) - - /** Data read */ - val s00_axi_rid = Output(UInt(1.W)) - val s00_axi_rdata = Output(UInt(32.W)) - val s00_axi_rresp = Output(UInt(2.W)) - val s00_axi_rlast = Output(Bool()) - val s00_axi_rvalid = Output(Bool()) - val s00_axi_rready = Input(Bool()) - - /** Clock and reset */ - val s00_axi_aclk = Input(Clock()) - val s00_axi_aresetn = Input(Reset()) - }) - - /** Verilog files in /src/main/resources - top file is mymem.v */ - addResource("/mymem.v") - addResource("/myaximem_v1_0.v") - addResource("/myaximem_v1_0_S00_AXI.v") -} - -/** Wrapper for mymem */ -class VivadoAXIMemory extends Slave(1, 10, 32) { - /** Instantiate a memory */ - val mem = Module(new mymem()) - - /** Connect it up with the slave's AXI interface */ - val aw = io.aw.bits - val dw = io.dw.bits - val wr = io.wr.bits - val ar = io.ar.bits - val dr = io.dr.bits - - /** Address write */ - mem.io.s00_axi_awid := aw.id - mem.io.s00_axi_awaddr := aw.addr - mem.io.s00_axi_awlen := aw.len - mem.io.s00_axi_awsize := aw.size - mem.io.s00_axi_awburst := aw.burst - mem.io.s00_axi_awlock := aw.lock - mem.io.s00_axi_awcache := aw.cache - mem.io.s00_axi_awprot := aw.prot - mem.io.s00_axi_awregion := aw.region - mem.io.s00_axi_awqos := aw.qos - mem.io.s00_axi_awvalid := io.aw.valid - io.aw.ready := mem.io.s00_axi_awready - - /** Data write */ - mem.io.s00_axi_wdata := dw.data - mem.io.s00_axi_wstrb := dw.strb - mem.io.s00_axi_wlast := dw.last - mem.io.s00_axi_wvalid := io.dw.valid - io.dw.ready := mem.io.s00_axi_wready - - /** Write response */ - wr.id := mem.io.s00_axi_bid - wr.resp := mem.io.s00_axi_bresp - io.wr.valid := mem.io.s00_axi_bvalid - mem.io.s00_axi_bready := io.wr.ready - - /** Address read */ - mem.io.s00_axi_arid := ar.id - mem.io.s00_axi_araddr := ar.addr - mem.io.s00_axi_arlen := ar.len - mem.io.s00_axi_arsize := ar.size - mem.io.s00_axi_arburst := ar.burst - mem.io.s00_axi_arlock := ar.lock - mem.io.s00_axi_arcache := ar.cache - mem.io.s00_axi_arprot := ar.prot - mem.io.s00_axi_arregion := ar.region - mem.io.s00_axi_arqos := ar.qos - mem.io.s00_axi_arvalid := io.ar.valid - io.ar.ready := mem.io.s00_axi_arready - - /** Data read */ - dr.id := mem.io.s00_axi_rid - dr.data := mem.io.s00_axi_rdata - dr.resp := mem.io.s00_axi_rresp - dr.last := mem.io.s00_axi_rlast - io.dr.valid := mem.io.s00_axi_rvalid - mem.io.s00_axi_rready := io.dr.ready - - /** Clock and reset */ - mem.io.s00_axi_aclk := clock - mem.io.s00_axi_aresetn := reset -} diff --git a/axi4/src/main/scala/axi4/AXI4FunctionalMaster.scala b/axi4/src/main/scala/axi4/AXI4FunctionalMaster.scala deleted file mode 100644 index d0bb460..0000000 --- a/axi4/src/main/scala/axi4/AXI4FunctionalMaster.scala +++ /dev/null @@ -1,470 +0,0 @@ -/** - * Author: Hans Jakob Damsgaard, hansjakobdamsgaard@gmail.com - * - * Purpose: Implementation of a testing framework for AXI4-compliant devices. - * - * Content: A functional AXI master implemented with ChiselTest. -*/ - -package axi4 - -import chisel3._ -import chisel3.util._ -import chiseltest._ -import chiseltest.internal.TesterThreadList -import scala.util.Random - -/** An AXI4 functional master - * - * @param dut a slave DUT - * - */ -class AXI4FunctionalMaster[T <: Slave](dut: T) { - /** DUT information */ - private[this] val idW = dut.idW - private[this] val addrW = dut.addrW - private[this] val dataW = dut.dataW - - /** Shortcuts to the channel IO */ - private[this] val aw = dut.io.aw - private[this] val dw = dut.io.dw - private[this] val wr = dut.io.wr - private[this] val ar = dut.io.ar - private[this] val dr = dut.io.dr - private[this] val resetn = dut.reset - private[this] val clk = dut.clock - - /** Threads and transaction state */ - // For writes - private[this] var awaitingWAddr = Seq[WriteTransaction]() - private[this] var awaitingWrite = Seq[WriteTransaction]() - private[this] var awaitingResp = Seq[WriteTransaction]() - private[this] var responses = Seq[Response]() - private[this] var wAddrT: TesterThreadList = _ - private[this] var writeT: TesterThreadList = _ - private[this] var respT: TesterThreadList = _ - // For reads - private[this] var awaitingRAddr = Seq[ReadTransaction]() - private[this] var awaitingRead = Seq[ReadTransaction]() - private[this] var readValues = Seq[Seq[BigInt]]() - private[this] var rAddrT: TesterThreadList = _ - private[this] var readT: TesterThreadList = _ - // For random data - private[this] val rng = new Random(42) - - /** Default values on all signals */ - // Address write - aw.bits.id.poke(0.U) - aw.bits.addr.poke(0.U) - aw.bits.len.poke(0.U) - aw.bits.size.poke(0.U) - aw.bits.burst.poke(BurstEncodings.Fixed) - aw.bits.lock.poke(LockEncodings.NormalAccess) - aw.bits.cache.poke(MemoryEncodings.DeviceNonbuf) - aw.bits.prot.poke(ProtectionEncodings.DataNsecUpriv) - aw.bits.qos.poke(0.U) - aw.bits.region.poke(0.U) - aw.valid.poke(false.B) - - // Data write - dw.bits.data.poke(0.U) - dw.bits.strb.poke(0.U) - dw.bits.last.poke(false.B) - dw.valid.poke(false.B) - - // Write response - wr.ready.poke(false.B) - - // Address read - ar.bits.id.poke(0.U) - ar.bits.addr.poke(0.U) - ar.bits.len.poke(0.U) - ar.bits.size.poke(0.U) - ar.bits.burst.poke(BurstEncodings.Fixed) - ar.bits.lock.poke(LockEncodings.NormalAccess) - ar.bits.cache.poke(MemoryEncodings.DeviceNonbuf) - ar.bits.prot.poke(ProtectionEncodings.DataNsecUpriv) - ar.bits.qos.poke(0.U) - ar.bits.region.poke(0.U) - ar.valid.poke(false.B) - - // Data read - dr.ready.poke(false.B) - - // Reset slave device controller - resetn.poke(false.B) - clk.step() - resetn.poke(true.B) - - /** Check for in-flight operations - * - * @return Boolean - */ - def hasInflightOps() = !awaitingWAddr.isEmpty || !awaitingWrite.isEmpty || !awaitingResp.isEmpty || !awaitingRAddr.isEmpty || !awaitingRead.isEmpty - - /** Check for responses or read data - * - * @return Boolean - */ - def hasRespOrReadData() = !responses.isEmpty || !readValues.isEmpty - - /** Handle the write address channel - * - * @note never call this method explicitly - */ - private[this] def writeAddrHandler(): Unit = { - println("New write address handler") - - /** Run this thread as long as the master is initialized or more transactions are waiting */ - while (!awaitingWAddr.isEmpty) { - /** Get the current transaction */ - val head = awaitingWAddr.head - - /** Write address to slave */ - aw.valid.poke(true.B) - aw.bits.id.poke(head.id.U) - aw.bits.addr.poke(head.addr.U) - aw.bits.len.poke(head.len.U) - aw.bits.size.poke(head.size.U) - aw.bits.burst.poke(head.burst) - aw.bits.lock.poke(head.lock) - aw.bits.cache.poke(head.cache) - aw.bits.prot.poke(head.prot) - aw.bits.qos.poke(head.qos) - aw.bits.region.poke(head.region) - while (!aw.ready.peek.litToBoolean) { - clk.step() - } - clk.step() - aw.valid.poke(false.B) - - /** Update transaction and queue */ - awaitingWAddr = awaitingWAddr.tail - head.addrSent = true - } - println("Closing write address handler") - } - - /** Handle the data write channel - * - * @note never call this method explicitly - */ - private[this] def writeHandler(): Unit = { - println("New write handler") - - /** Run this thread as long as the master is initialized or more transactions are waiting */ - while (!awaitingWrite.isEmpty) { - /** Get the current transaction */ - val head = awaitingWrite.head - while (!head.addrSent) { - clk.step() - } - - /** Write data to slave */ - dw.valid.poke(true.B) - while (!head.complete) { - val (data, strb, last) = head.next - println("Write " + data.litValue + " with strobe " + strb.toString + " and last " + last.litToBoolean) - dw.bits.data.poke(data) - dw.bits.strb.poke(strb) - dw.bits.last.poke(last) - while (!dw.ready.peek.litToBoolean) { - clk.step() - } - clk.step() - } - dw.valid.poke(false.B) - - /** Update transaction and queue */ - awaitingWrite = awaitingWrite.tail - head.dataSent = true - } - println("Closing write handler") - } - - /** Watch the response channel - * - * @note never call this method explicitly - */ - private[this] def respHandler() = { - println("New response handler") - - /** Run this thread as long as the master is initialized or more transactions are waiting */ - while (!awaitingResp.isEmpty) { - /** Get the current transaction */ - val head = awaitingResp.head - while (!head.dataSent) { - clk.step() - } - - /** Indicate that interface is ready and wait for response */ - wr.ready.poke(true.B) - while (!wr.valid.peek.litToBoolean) { - clk.step() - } - responses = responses :+ (new Response(wr.bits.resp.peek, wr.bits.id.peek.litValue)) - wr.ready.poke(false.B) - - /** Update queue */ - awaitingResp = awaitingResp.tail - } - println("Closing response handler") - } - - /** Handle the read address channel - * - * @note never call this method explicitly - */ - private[this] def readAddrHandler(): Unit = { - println("New read address handler") - - /** Run this thread as long as the master is initialized or more transactions are waiting */ - while (!awaitingRAddr.isEmpty) { - /** Get the current transaction */ - val head = awaitingRAddr.head - - /** Write address to slave */ - ar.valid.poke(true.B) - ar.bits.id.poke(head.id.U) - ar.bits.addr.poke(head.addr.U) - ar.bits.len.poke(head.len.U) - ar.bits.size.poke(head.size.U) - ar.bits.burst.poke(head.burst) - ar.bits.lock.poke(head.lock) - ar.bits.cache.poke(head.cache) - ar.bits.prot.poke(head.prot) - ar.bits.qos.poke(head.qos) - ar.bits.region.poke(head.region) - while (!ar.ready.peek.litToBoolean) { - clk.step() - } - clk.step() - ar.valid.poke(false.B) - - /** Update transaction and queue */ - awaitingRAddr = awaitingRAddr.tail - head.addrSent = true - } - println("Closing read address handler") - } - - /** Handle the data read channel - * - * @note never call this method explicitly - */ - private[this] def readHandler(): Unit = { - println("New read handler") - - /** Run this thread as long as the master is initialized or more transactions are waiting */ - while (!awaitingRead.isEmpty) { - /** Get the current transaction */ - val head = awaitingRead.head - while (!head.addrSent) { - clk.step() - } - - /** Read data from slave */ - dr.ready.poke(true.B) - while (!head.complete) { - if (dr.valid.peek.litToBoolean) { - val (data, resp, last) = (dr.bits.data.peek, dr.bits.resp.peek, dr.bits.last.peek) - println(s"Read " + data.litValue + " with response " + resp.litValue + " and last " + last.litToBoolean) - head.add(data.litValue) - } - clk.step() - } - readValues = readValues :+ head.data - dr.ready.poke(false.B) - - /** Update queue */ - awaitingRead = awaitingRead.tail - } - println("Closing read handler") - } - - /** Destructor - * - * @note joins all non-null thread pointers and checks for responses and read data waiting in queues - */ - override def finalize() = { - /** Join handlers */ - if (wAddrT != null) wAddrT.join() - if (writeT != null) writeT.join() - if (respT != null) respT.join() - if (rAddrT != null) rAddrT.join() - if (readT != null) readT.join() - - /** Check for unchecked responses and read data */ - if (hasRespOrReadData) println(s"WARNING: master had ${responses.length} responses and ${readValues.length} Seq's of read data waiting") - } - - /** Start a write transaction to the given address - * - * @param addr start write address - * @param data optional list of data to write, defaults to random data - * @param id optional id, defaults to ID 0 - * @param len optional burst length, defaults to 0 (i.e., 1 beat) - * @param size optional beat size, defaults to 1 byte - * @param burst optional burst type, defaults to FIXED - * @param lock optional lock type, defaults to normal access - * @param cache optional memory attribute signal, defaults to device non-bufferable - * @param prot optional protection type, defaults to non-secure unprivileged data access - * @param qos optional QoS, defaults to 0 - * @param region optional region, defaults to 0 - * - * @note [[addr]] must fit within the slave DUT's write address width - * @note entries in [[data]] must fit within the slave DUT's write data width, and the list can have at most [[len]] entries - * @note [[id]] must fit within DUT's ID width, likewise [[size]] cannot be greater than the DUT's write data width - * @note [[burst]], [[lock]], [[cache]], and [[prot]] should be a set of those defined in Defs.scala - */ - def createWriteTrx( - addr: BigInt, - data: Seq[BigInt] = Seq[BigInt](), - id: BigInt = 0, - len: Int = 0, - size: Int = 0, - burst: UInt = BurstEncodings.Fixed, - lock: Bool = LockEncodings.NormalAccess, - cache: UInt = MemoryEncodings.DeviceNonbuf, - prot: UInt = ProtectionEncodings.DataNsecUpriv, - qos: UInt = 0.U, - region: UInt = 0.U) = { - require(log2Up(addr) <= addrW, s"address must fit within DUT's write address width (got $addr)") - require(log2Up(id) <= idW, s"ID must fit within DUT's ID width (got $id)") - - /** [[len]] and [[size]] checks - * - [[size]] must be less than or equal to the write data width - * - [[len]] must be <= 15 for FIXED and WRAP transactions, only INCR can go beyond - * - Bursts cannot cross 4KB boundaries - */ - val startAddr = addr - val numBytes = 1 << size - val burstLen = len + 1 - val alignedAddr = (startAddr / numBytes) * numBytes - val wrapBoundary = (startAddr / (numBytes * burstLen)) * (numBytes * burstLen) - require(numBytes <= dataW, s"size must be less than or equal to the write data width") - burst match { - case BurstEncodings.Fixed => - require(burstLen <= 16, s"len for FIXED transactions must be less than or equal to 15 (got $len)") - require(((startAddr + numBytes) >> 12) == (startAddr >> 12), "burst cannot cross 4KB boundary") - case BurstEncodings.Incr => - require(burstLen <= 256, s"len for INCR transactions must be less than or equal to 255 (got $len)") - require(((startAddr + numBytes * burstLen) >> 12) == (startAddr >> 12), "burst cannot cross 4KB boundary") - case BurstEncodings.Wrap => - require(burstLen <= 16, s"len for WRAP transactions must be less than or equal to 15 (got $len)") - require((startAddr >> 12) == (wrapBoundary >> 12), "burst cannot cross 4KB boundary") - case _ => throw new IllegalArgumentException("invalid burst type entered") - } - - /** Select data */ - val tdata = if (data != Nil) { - require(data.length == burstLen, "given data length should match burst length") - data - } else - Seq.fill(burstLen) { BigInt(numBytes, rng) } - - /** Create and queue new write transaction */ - val trx = new WriteTransaction(addr, data, dataW, id, len, size, burst, lock, cache, prot, qos, region) - awaitingWAddr = awaitingWAddr :+ trx - awaitingWrite = awaitingWrite :+ trx - awaitingResp = awaitingResp :+ trx - - /** If this was the first transaction, fork new handlers */ - if (awaitingWAddr.length == 1) wAddrT = fork { writeAddrHandler() } - if (awaitingWrite.length == 1) writeT = fork { writeHandler() } - if (awaitingResp.length == 1) respT = fork { respHandler() } - } - - /** Start a write transaction to the given address - * - * @param addr start read address - * @param id optional id, defaults to ID 0 - * @param len optional burst length, defaults to 0 (i.e., 1 beat) - * @param size optional beat size, defaults to 1 byte - * @param burst optional burst type, defaults to FIXED - * @param lock optional lock type, defaults to normal access - * @param cache optional memory attribute signal, defaults to device non-bufferable - * @param prot optional protection type, defaults to non-secure unprivileged data access - * @param qos optional QoS, defaults to 0 - * @param region optional region, defaults to 0 - * - * @note [[addr]] must fit within the slave DUT's write address width - * @note [[id]] must fit within DUT's ID width, likewise [[size]] cannot be greater than the DUT's write data width - * @note [[burst]], [[lock]], [[cache]], and [[prot]] should be a set of those defined in Defs.scala - */ - def createReadTrx( - addr: BigInt, - id: BigInt = 0, - len: Int = 0, - size: Int = 0, - burst: UInt = BurstEncodings.Fixed, - lock: Bool = LockEncodings.NormalAccess, - cache: UInt = MemoryEncodings.DeviceNonbuf, - prot: UInt = ProtectionEncodings.DataNsecUpriv, - qos: UInt = 0.U, - region: UInt = 0.U) = { - require(log2Up(addr) <= addrW, s"address must fit within DUT's write address width (got $addr)") - require(log2Up(id) <= idW, s"ID must fit within DUT's ID width (got $id)") - - /** [[len]] and [[size]] checks - * - [[size]] must be less than or equal to the write data width - * - [[len]] must be <= 15 for FIXED and WRAP transactions, only INCR can go beyond - * - Bursts cannot cross 4KB boundaries - */ - val startAddr = addr - val numBytes = 1 << size - val burstLen = len + 1 - val alignedAddr = (startAddr / numBytes) * numBytes - val wrapBoundary = (startAddr / (numBytes * burstLen)) * (numBytes * burstLen) - require(numBytes <= dataW, s"size must be less than or equal to the write data width") - burst match { - case BurstEncodings.Fixed => - require(burstLen <= 16, s"len for FIXED transactions must be less than or equal to 15 (got $len)") - require(((startAddr + numBytes) >> 12) == (startAddr >> 12), "burst cannot cross 4KB boundary") - case BurstEncodings.Incr => - require(burstLen <= 256, s"len for INCR transactions must be less than or equal to 255 (got $len)") - require(((startAddr + numBytes * burstLen) >> 12) == (startAddr >> 12), "burst cannot cross 4KB boundary") - case BurstEncodings.Wrap => - require(burstLen <= 16, s"len for WRAP transactions must be less than or equal to 15 (got $len)") - require((startAddr >> 12) == (wrapBoundary >> 12), "burst cannot cross 4KB boundary") - case _ => throw new IllegalArgumentException("invalid burst type entered") - } - - /** Create and queue new read transaction */ - val trx = new ReadTransaction(addr, id, len, size, burst, lock, cache, prot, qos, region) - awaitingRAddr = awaitingRAddr :+ trx - awaitingRead = awaitingRead :+ trx - - /** If this was the first transaction, fork new handlers */ - if (awaitingRAddr.length == 1) rAddrT = fork { readAddrHandler() } - if (awaitingRead.length == 1) readT = fork { readHandler() } - } - - /** Check for write response - * - * @note write responses are continuously stored in an internal queue by a second thread - * @note reading is destructive; i.e., the response being checked is removed from the queue - */ - def checkResponse() = { - responses match { - case r :: tail => - responses = tail - Some(r) - case _ => None - } - } - - /** Check for read data - * - * @note read values are continuously stored in an internal queue by a second thread spawned when creating a new read transaction - * @note reading is destructive; i.e., the data being returned is removed from the queue - */ - def checkReadData() = { - readValues match { - case v :: tail => - readValues = tail - Some(v) - case _ => None - } - } -} diff --git a/axi4/src/main/scala/axi4/Defs.scala b/axi4/src/main/scala/axi4/Defs.scala deleted file mode 100644 index c3f7434..0000000 --- a/axi4/src/main/scala/axi4/Defs.scala +++ /dev/null @@ -1,228 +0,0 @@ -/** - * Author: Hans Jakob Damsgaard, hansjakobdamsgaard@gmail.com - * - * Purpose: Implementation of a testing framework for AXI4-compliant devices. - * - * Content: Interface definitions for AXI4. -*/ - -package axi4 - -import chisel3._ -import chisel3.util._ - -/** Constant naming according to https://docs.scala-lang.org/style/naming-conventions.html */ - -/** AXI4 burst encodings */ -object BurstEncodings { - val Fixed = "b00".U - val Incr = "b01".U - val Wrap = "b10".U -} - -/** AXI lock encodings */ -object LockEncodings { - val NormalAccess = false.B - val ExclusiveAccess = true.B -} - -/** AXI4 memory encodings */ -object MemoryEncodings { - val DeviceNonbuf = "b0000".U - val DeviceBuf = "b0001".U - val NormalNonbuf = "b0010".U - val NormalBuf = "b0011".U - val WtNoalloc = "b0110".U - val WtReadalloc = "b0110".U - val WtWritealloc = "b1110".U - val WtRwalloc = "b1110".U - val WbNoalloc = "b0111".U - val WbReadalloc = "b0111".U - val WbWritealloc = "b1111".U - val WbRwalloc = "b1111".U -} - -/** AXI4 protection encodings */ -object ProtectionEncodings { - val DataSecUpriv = "b000".U - val DataSecPriv = "b001".U - val DataNsecUpriv = "b010".U - val DataNsecPriv = "b011".U - val InstrSecUpriv = "b100".U - val InstrSecPriv = "b101".U - val InstrNsecUpriv = "b110".U - val InstrNsecPriv = "b111".U -} - -/** AXI4 response encodings */ -object ResponseEncodings { - val Okay = "b00".U - val Exokay = "b01".U - val Slverr = "b10".U - val Decerr = "b11".U -} - -/** User signal trait */ -trait UserSignal { - val user = Output(UInt()) -} - -/** AXI4 write address base interface - * - * Defines the mandatory signals in the interface - * - * @param idW the width of the AWID signal in bits - * @param addrW the width of the AWADDR signal in bits - */ -abstract class AXI4WABase(val idW: Int, val addrW: Int) extends Bundle { - require(idW > 0, "the id width must be a positive integer") - require(addrW > 0, "the address width must be a positive integer") - val id = Output(UInt(idW.W)) - val addr = Output(UInt(addrW.W)) - val len = Output(UInt(8.W)) - val size = Output(UInt(3.W)) - val burst = Output(UInt(2.W)) - val lock = Output(Bool()) - val cache = Output(UInt(4.W)) - val prot = Output(UInt(3.W)) - val qos = Output(UInt(4.W)) - val region = Output(UInt(4.W)) -} -class AXI4WA(idW: Int, addrW: Int) extends AXI4WABase(idW, addrW) -class AXI4WAUser(idW: Int, addrW: Int) extends AXI4WA(idW, addrW) with UserSignal - -/** AXI4 write data base interface - * - * Defines the mandatory signals in the interface - * - * @param dataW the width of the WDATA signal in bits - */ -abstract class AXI4WDBase(val dataW: Int) extends Bundle { - require(dataW > 0, "the data width must be a positive integer") - require(isPow2(dataW / 8), "the data width must be a power of 2 multiple of bytes") - val data = Output(UInt(dataW.W)) - val strb = Output(UInt((dataW/8).W)) - val last = Output(Bool()) -} -class AXI4WD(dataW: Int) extends AXI4WDBase(dataW) -class AXI4WDUser(dataW: Int) extends AXI4WD(dataW) with UserSignal - -/** AXI4 write response base interface - * - * Defines the mandatory signals in the interface - * - * @param idW the width of the BID signal in bits - */ -abstract class AXI4WRBase(val idW: Int) extends Bundle { - require(idW > 0, "the id width must be a positive integer") - val id = Input(UInt(idW.W)) - val resp = Input(UInt(2.W)) -} -class AXI4WR(idW: Int) extends AXI4WRBase(idW) -class AXI4WRUser(idW: Int) extends AXI4WR(idW) with UserSignal - -/** AXI4 read address base interface - * - * Defines the mandatory signals in the interface - * - * @param idW the width of the ARID signal in bits - * @param addrW the width of the ARADDR signal in bits - */ -abstract class AXI4RABase(val idW: Int, val addrW: Int) extends Bundle { - require(idW > 0, "the id width must be a positive integer") - require(addrW > 0, "the address width must be a positive integer") - val id = Output(UInt(idW.W)) - val addr = Output(UInt(addrW.W)) - val len = Output(UInt(8.W)) - val size = Output(UInt(3.W)) - val burst = Output(UInt(2.W)) - val lock = Output(Bool()) - val cache = Output(UInt(4.W)) - val prot = Output(UInt(3.W)) - val qos = Output(UInt(4.W)) - val region = Output(UInt(4.W)) -} -class AXI4RA(idW: Int, addrW: Int) extends AXI4RABase(idW, addrW) -class AXI4RAUser(idW: Int, addrW: Int) extends AXI4RA(idW, addrW) with UserSignal - -/** AXI4 read data base interface - * - * Defines the mandatory signals in the interface - * - * @param idW the width of the RID signal in bits - * @param dataW the width of the RDATA signal in bits - */ -abstract class AXI4RDBase(val idW: Int, val dataW: Int) extends Bundle { - require(idW > 0, "the id width must be a positive integer") - require(isPow2(dataW / 8), "the data width must be a power of 2 multiple of bytes") - val id = Input(UInt(idW.W)) - val data = Input(UInt(dataW.W)) - val resp = Input(UInt(2.W)) - val last = Input(Bool()) -} -class AXI4RD(idW: Int, dataW: Int) extends AXI4RDBase(idW, dataW) -class AXI4RDUser(idW: Int, dataW: Int) extends AXI4RD(idW, dataW) with UserSignal - -/** AXI4 master interface - * - * Components meant to use this interface should extend it - * - * @param idW the width of the ID signals in bits - * @param addrW the width of the address signals in bits - * @param dataW the width of the data read/write signals in bits - */ -abstract class AXI4MasterBase(val idW: Int, val addrW: Int, val dataW: Int) extends Bundle { - /** Fields implementing each of the AXI channels - * - * [[aw]] is the write address channel - * [[dw]] is the write data channel - * [[wr]] is the write response channel - * [[ar]] is the read address channel - * [[dr]] is the read data channel - */ - val aw = Decoupled(new AXI4WA(idW, addrW)) - val dw = Decoupled(new AXI4WD(dataW)) - val wr = Flipped(Decoupled(Flipped(new AXI4WR(idW)))) - val ar = Decoupled(new AXI4RA(idW, addrW)) - val dr = Flipped(Decoupled(Flipped(new AXI4RD(idW, dataW)))) -} -class AXI4Master(idW: Int, addrW: Int, dataW: Int) extends AXI4MasterBase(idW, addrW, dataW) -class AXI4MasterUser(idW: Int, addrW: Int, dataW: Int) extends AXI4Master(idW, addrW, dataW) { - override val aw = Decoupled(new AXI4WAUser(idW, addrW)) - override val dw = Decoupled(new AXI4WDUser(dataW)) - override val wr = Flipped(Decoupled(Flipped(new AXI4WRUser(idW)))) - override val ar = Decoupled(new AXI4RAUser(idW, addrW)) - override val dr = Flipped(Decoupled(Flipped(new AXI4RDUser(idW, dataW)))) -} - -/** AXI4 slave interface - * - * Components meant to use this interface should extend it - * - * @param idW the width of the ID signals in bits - * @param addrW the width of the address signals in bits - * @param dataW the width of the data read/write signals in bits - */ -abstract class AXI4SlaveBase(val idW: Int, val addrW: Int, val dataW: Int) extends Bundle { - /** Fields implementing each of the AXI channels - * - * [[aw]] is the write address channel - * [[dw]] is the write data channel - * [[wr]] is the write response channel - * [[ar]] is the read address channel - * [[dr]] is the read data channel - */ - val aw = Flipped(Decoupled(new AXI4WA(idW, addrW))) - val dw = Flipped(Decoupled(new AXI4WD(dataW))) - val wr = Flipped(Flipped(Decoupled(Flipped(new AXI4WR(idW))))) - val ar = Flipped(Decoupled(new AXI4RA(idW, addrW))) - val dr = Flipped(Flipped(Decoupled(Flipped(new AXI4RD(idW, dataW))))) -} -class AXI4Slave(idW: Int, addrW: Int, dataW: Int) extends AXI4SlaveBase(idW, addrW, dataW) -class AXI4SlaveUser(idW: Int, addrW: Int, dataW: Int) extends AXI4Slave(idW, addrW, dataW) { - override val aw = Flipped(Decoupled(new AXI4WAUser(idW, addrW))) - override val dw = Flipped(Decoupled(new AXI4WDUser(dataW))) - override val wr = Flipped(Flipped(Decoupled(Flipped(new AXI4WRUser(idW))))) - override val ar = Flipped(Decoupled(new AXI4RAUser(idW, addrW))) - override val dr = Flipped(Flipped(Decoupled(Flipped(new AXI4RDUser(idW, dataW))))) -} diff --git a/axi4/src/main/scala/axi4/Master.scala b/axi4/src/main/scala/axi4/Master.scala deleted file mode 100644 index 249f5db..0000000 --- a/axi4/src/main/scala/axi4/Master.scala +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Author: Hans Jakob Damsgaard, hansjakobdamsgaard@gmail.com - * - * Purpose: Implementation of a testing framework for AXI4-compliant devices. - * - * Content: An empty AXI master with relevant interface. -*/ - -package axi4 - -import chisel3._ - -/** AXI4 master - * - * An empty class representing an AXI master - * - * @param idW the width of the ID signals in bits - * @param addrW the width of the address signals in bits - * @param dataW the width of the data read/write signals in bits - */ -abstract class Master(val idW: Int, val addrW: Int, val dataW: Int) extends Module { - val io = IO(new AXI4Master(idW, addrW, dataW)) -} - -/** AXI4 master with user signals - * - * An empty class representing an AXI master with user signals - * - * @param idW the width of the ID signals in bits - * @param addrW the width of the address signals in bits - * @param dataW the width of the data read/write signals in bits - */ -abstract class MasterUser(override val idW: Int, override val addrW: Int, override val dataW: Int) extends Master(idW, addrW, dataW) { - override val io = IO(new AXI4MasterUser(idW, addrW, dataW)) -} diff --git a/axi4/src/main/scala/axi4/Slave.scala b/axi4/src/main/scala/axi4/Slave.scala deleted file mode 100644 index 8f89e02..0000000 --- a/axi4/src/main/scala/axi4/Slave.scala +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Author: Hans Jakob Damsgaard, hansjakobdamsgaard@gmail.com - * - * Purpose: Implementation of a testing framework for AXI4-compliant devices. - * - * Content: An empty AXI slave with relevant interface. -*/ - -package axi4 - -import chisel3._ - -/** AXI4 slave - * - * An empty class representing an AXI slave - * - * @param idW the width of the ID signals in bits - * @param addrW the width of the address signals in bits - * @param dataW the width of the data read/write signals in bits - */ -abstract class Slave(val idW: Int, val addrW: Int, val dataW: Int) extends Module { - val io = IO(new AXI4Slave(idW, addrW, dataW)) -} - -/** AXI4 slave with user signals - * - * An empty class representing an AXI slave with user signals - * - * @param idW the width of the ID signals in bits - * @param addrW the width of the address signals in bits - * @param dataW the width of the data read/write signals in bits - */ -abstract class SlaveUser(override val idW: Int, override val addrW: Int, override val dataW: Int) extends Slave(idW, addrW, dataW) { - override val io = IO(new AXI4SlaveUser(idW, addrW, dataW)) -} diff --git a/axi4/src/main/scala/axi4/Transaction.scala b/axi4/src/main/scala/axi4/Transaction.scala deleted file mode 100644 index f3484d3..0000000 --- a/axi4/src/main/scala/axi4/Transaction.scala +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Author: Hans Jakob Damsgaard, hansjakobdamsgaard@gmail.com - * - * Purpose: Implementation of a testing framework for AXI4-compliant devices. - * - * Content: Transaction classes for functional masters and slaves. -*/ - -package axi4 - -import chisel3._ - -/** Transaction superclass */ -trait Transaction { - def complete: Boolean -} - -/** Write transaction - * - * @param addr start write address - * @param data list of data to write - * @param dataW slave's data bus width - * @param id optional id, defaults to ID 0 - * @param len optional burst length, defaults to 0 (i.e., 1 beat) - * @param size optional beat size, defaults to 1 byte - * @param burst optional burst type, defaults to FIXED - * @param lock optional lock type, defaults to normal access - * @param cache optional memory attribute signal, defaults to device non-bufferable - * @param prot optional protection type, defaults to non-secure unprivileged data access - * @param qos optional QoS, defaults to 0 - * @param region optional region, defaults to 0 - */ -class WriteTransaction( - val addr: BigInt, - val data: Seq[BigInt], - dataW: Int, - val id: BigInt = 0, - val len: Int = 0, - val size: Int = 0, - val burst: UInt = BurstEncodings.Fixed, - val lock: Bool = LockEncodings.NormalAccess, - val cache: UInt = MemoryEncodings.DeviceNonbuf, - val prot: UInt = ProtectionEncodings.DataNsecUpriv, - val qos: UInt = 0.U, - val region: UInt = 0.U) extends Transaction { - private[this] val numBytes = 1 << size - private[this] val dtsize = numBytes * data.length - private[this] val lowerBoundary = (addr / dtsize) * dtsize - private[this] val upperBoundary = lowerBoundary + dtsize - private[this] val alignedAddress = ((addr / numBytes) * numBytes) - private[this] var aligned = addr == alignedAddress - private[this] var address = addr - private[this] var count = 0 - - private[this] var _addrSent = false - private[this] var _dataSent = false - - /** Getter and setter for [[addrSent]] */ - def addrSent = _addrSent - def addrSent_=(newValue: Boolean): Unit = _addrSent = newValue - - /** Getter and setter for [[dataSent]] */ - def dataSent = _dataSent - def dataSent_=(newValue: Boolean): Unit = _dataSent = newValue - - /** Get next (data, strb, last) tuple - * - * @return (data, strb, last) tuple - * - * @note has side effect on internal index count - */ - def next() = { - /** Strobe calculation */ - val offset = (address / dataW) * dataW - val lowerByteLane = address - offset - val upperByteLane = if (aligned) lowerByteLane + numBytes-1 else alignedAddress + numBytes-1 - offset - def within(x: Int) = x >= 0 && x <= (upperByteLane - lowerByteLane) - val strb = ("b"+(0 until (dataW/8)).foldRight("") { (elem, acc) => if (within(elem)) acc + "1" else acc + "0" }).asUInt - - /** Update address */ - if (burst != BurstEncodings.Fixed) { - if (aligned) { - address += numBytes - if (burst == BurstEncodings.Wrap) { - if (address >= upperBoundary) { - address = lowerBoundary - } - } - } else { - address += numBytes - aligned = true - } - } - count += 1 - - /** Return data to write */ - (data(count-1).U, strb, complete.B) - } - def complete = data.length == count -} - -/** Read transaction - * - * @param addr start read address - * @param id optional id, defaults to ID 0 - * @param len optional burst length, defaults to 0 (i.e., 1 beat) - * @param size optional beat size, defaults to 1 byte - * @param burst optional burst type, defaults to FIXED - * @param lock optional lock type, defaults to normal access - * @param cache optional memory attribute signal, defaults to device non-bufferable - * @param prot optional protection type, defaults to non-secure unprivileged data access - * @param qos optional QoS, defaults to 0 - * @param region optional region, defaults to 0 - */ -class ReadTransaction( - val addr: BigInt, - val id: BigInt = 0, - val len: Int = 0, - val size: Int = 0, - val burst: UInt = BurstEncodings.Fixed, - val lock: Bool = LockEncodings.NormalAccess, - val cache: UInt = MemoryEncodings.DeviceNonbuf, - val prot: UInt = ProtectionEncodings.DataNsecUpriv, - val qos: UInt = 0.U, - val region: UInt = 0.U) extends Transaction { - var data = Seq[BigInt]() - - private[this] var _addrSent = false - - /** Getter and setter for [[addrSent]] */ - def addrSent = _addrSent - def addrSent_=(newValue: Boolean): Unit = _addrSent = newValue - - /** Add element to data sequence - * - * @param v value to add - * - * @note has side effect on internal data sequence - */ - def add(v: BigInt) = { - data = data :+ v - } - def complete = data.length == (len + 1) -} - -/** Transaction response - * - * @param resp transaction response - * @param id optional id - */ -case class Response(val resp: UInt, val id: BigInt = 0) diff --git a/axi4/src/test/scala/VivadoAXIMemoryTester.scala b/axi4/src/test/scala/VivadoAXIMemoryTester.scala deleted file mode 100644 index 51a934b..0000000 --- a/axi4/src/test/scala/VivadoAXIMemoryTester.scala +++ /dev/null @@ -1,247 +0,0 @@ -/** - * Author: Hans Jakob Damsgaard, hansjakobdamsgaard@gmail.com - * - * Purpose: Implementation of a testing framework for AXI4-compliant devices. - * - * Content: A tester for an AXI4 interfaced Vivado BRAM IP. -*/ - -import axi4._ -import chisel3._ -import chiseltest._ -import chiseltest.experimental.TestOptionBuilder._ -import chiseltest.internal.VerilatorBackendAnnotation -import org.scalatest._ -import scala.util.Random - -class VivadoAXIMemoryTester extends FlatSpec with ChiselScalatestTester with Matchers { - behavior of "AXI4 BRAM" - - it should "initialize" in { - test(new VivadoAXIMemory()).withAnnotations(Seq(VerilatorBackendAnnotation)) { - dut => - val master = new AXI4FunctionalMaster(dut) - } - } - - it should "write and read manually" in { - test(new VivadoAXIMemory()).withAnnotations(Seq(VerilatorBackendAnnotation)) { - dut => - val master = new AXI4FunctionalMaster(dut) - - def printCheck() = { - println("AWREADY = " + dut.io.aw.ready.peek.litToBoolean) - println("WREADY = " + dut.io.dw.ready.peek.litToBoolean) - println("BVALID = " + dut.io.wr.valid.peek.litToBoolean) - println("ARREADY = " + dut.io.ar.ready.peek.litToBoolean) - println("RVALID = " + dut.io.dr.valid.peek.litToBoolean) - } - - // Set some initial values on the necessary signals - dut.io.dw.bits.data.poke(42.U) - dut.io.dw.bits.strb.poke("b1111".U) - dut.io.dw.bits.last.poke(true.B) - printCheck() - - // Write address - dut.io.aw.valid.poke(true.B) - do { - dut.clock.step() - } while (!dut.io.aw.ready.peek.litToBoolean) - printCheck() - dut.clock.step() - dut.io.aw.valid.poke(false.B) - - // Write some data - dut.io.dw.valid.poke(true.B) - do { - dut.clock.step() - } while (!dut.io.dw.ready.peek.litToBoolean) - printCheck() - dut.clock.step() - dut.io.dw.valid.poke(false.B) - - // Fetch response - dut.io.wr.ready.poke(true.B) - while (!dut.io.wr.valid.peek.litToBoolean) { - dut.clock.step() - } - printCheck() - val r = dut.io.wr.bits.resp.peek.litValue - println(s"Got response $r") - - // Read address - dut.io.ar.valid.poke(true.B) - do { - dut.clock.step() - } while (!dut.io.ar.ready.peek.litToBoolean) - printCheck() - dut.clock.step() - dut.io.ar.valid.poke(false.B) - - // Read some data - dut.io.dr.ready.poke(true.B) - while (!dut.io.dr.valid.peek.litToBoolean) { - dut.clock.step() - } - printCheck() - dut.io.dr.bits.data.expect(42.U) - } - } - - it should "write and read with FIXED transactions" in { - test(new VivadoAXIMemory()).withAnnotations(Seq(VerilatorBackendAnnotation)) { - dut => - val master = new AXI4FunctionalMaster(dut) - - // Create write transaction - master.createWriteTrx(0, Seq(42), size = 2) - - // Wait for the write to complete (spin on response) - var resp = master.checkResponse() - while (resp == None) { - resp = master.checkResponse() - dut.clock.step() - } - val r = resp match { case Some(r) => r.resp.litValue; case _ => ResponseEncodings.Slverr.litValue } - assert(r == 0, "expected write to pass") - - // Create read transaction - master.createReadTrx(0, size = 2) - - // Wait for read to complete (spin on read data) - var data = master.checkReadData() - while (data == None) { - data = master.checkReadData() - dut.clock.step() - } - val d = data match { case Some(v) => v; case _ => Seq() } - assert(d.length == 1 && d(0) == 42, "read data value is incorrect") - } - } - - it should "write and read with INCR transactions" in { - test(new VivadoAXIMemory()).withAnnotations(Seq(VerilatorBackendAnnotation)) { - dut => - val master = new AXI4FunctionalMaster(dut) - - // Create write transaction - master.createWriteTrx(128, Seq.fill(128)(0x7FFFFFFF), len = 0x7F, size = 2, burst = BurstEncodings.Incr) - - // Wait for the write to complete - var resp = master.checkResponse() - while (resp == None) { - resp = master.checkResponse() - dut.clock.step() - } - val r = resp match { case Some(r) => r.resp.litValue; case _ => ResponseEncodings.Slverr.litValue } - assert(r == 0, "expected write to pass") - - // Create read transaction - master.createReadTrx(128, len = 0x7F, size = 2, burst = BurstEncodings.Incr) - - // Wait for read to complete - var data = master.checkReadData() - while (data == None) { - data = master.checkReadData() - dut.clock.step() - } - val d = data match { case Some(v) => v; case _ => Seq() } - assert(d.foldLeft(true) { (acc, elem) => acc && (elem == 0x7FFFFFFF) }, "read data value is incorrect") - } - } - - it should "write and read with WRAP transactions" in { - test(new VivadoAXIMemory()).withAnnotations(Seq(VerilatorBackendAnnotation)) { - dut => - val master = new AXI4FunctionalMaster(dut) - - // addr = 96 - // dtsize = 4 * 16 = 64 - // Lower Wrap Boundary = INT(addr / dtsize) * dtsize = 64 - // Upper Wrap Boundary = Lower Wrap Boundary + dtsize = 128 - val inputData = (0 to 15).toSeq.map(x => BigInt(x)) - val outputData = inputData.takeRight(8) ++ inputData.take(8) - - // Create write transaction - master.createWriteTrx(96, inputData, len = 0xF, size = 2, burst = BurstEncodings.Wrap) - - // Wait for the write to complete - var resp = master.checkResponse() - while (resp == None) { - resp = master.checkResponse() - dut.clock.step() - } - val r = resp match { case Some(r) => r.resp.litValue; case _ => ResponseEncodings.Slverr.litValue } - assert(r == 0, "expected write to pass") - - // Create read transaction - master.createReadTrx(64, len = 0xF, size = 2, burst = BurstEncodings.Incr) - - // Wait for read to complete - var data = master.checkReadData() - while (data == None) { - data = master.checkReadData() - dut.clock.step() - } - val d = data match { case Some(v) => v; case _ => Seq() } - assert(d.zip(outputData).map { x => x._1 == x._2 }.foldLeft(true) { (acc, elem) => acc && elem }, "read data value is incorrect") - } - } - - it should "handle multiple in-flight transactions" in { - test(new VivadoAXIMemory()).withAnnotations(Seq(VerilatorBackendAnnotation)) { - dut => - val master = new AXI4FunctionalMaster(dut) - - // Create two write transactions - val rng = new Random(42) - val data1 = Seq.fill(32) { BigInt(32, rng) } - val data2 = Seq.fill(16) { BigInt(32, rng) } - master.createWriteTrx(512, data1, len = 0x1F, size = 2, burst = BurstEncodings.Incr) - master.createWriteTrx(192, data2, len = 0xF, size = 1) - - // Wait for the first write to complete - var resp = master.checkResponse() - while (resp == None) { - resp = master.checkResponse() - dut.clock.step() - } - var r = resp match { case Some(r) => r.resp.litValue; case _ => ResponseEncodings.Slverr.litValue } - assert(r == 0, "expected write to pass") - - // Create first read transaction - master.createReadTrx(512, len = 0x1F, size = 2, burst = BurstEncodings.Incr) - - // Wait for the first read to complete - var data = master.checkReadData() - while (data == None) { - data = master.checkReadData() - dut.clock.step() - } - var d = data match { case Some(v) => v; case _ => Seq() } - assert(d.zip(data1).foldLeft(true) { (acc, elem) => acc && (elem._1 == elem._2) }, "read data value is incorrect") - - // Wait for the second write to complete - resp = master.checkResponse() - while (resp == None) { - resp = master.checkResponse() - dut.clock.step() - } - r = resp match { case Some(r) => r.resp.litValue; case _ => ResponseEncodings.Slverr.litValue } - assert(r == 0, "expected write to pass") - - // Create second read transaction - master.createReadTrx(192, size = 1) - - // Wait for the second read to complete - data = master.checkReadData() - while (data == None) { - data = master.checkReadData() - dut.clock.step() - } - d = data match { case Some(v) => v; case _ => Seq() } - assert(d(0) == (data2.last & ((1 << 16) - 1)), "read data value is incorrect") - } - } -} diff --git a/lab1/README.md b/lab1/README.md index 994767f..d6eaf74 100644 --- a/lab1/README.md +++ b/lab1/README.md @@ -33,6 +33,5 @@ With IntelliJ import the lab2 project as follows: * Click *Import Project*, or on a running IntelliJ: *File - New - Project from Existing Source...* * Navigate to ```.../class2020/lab1``` and select the file ```build.sbt```, press *Open* - * Make sure to select JDK 1.8 (not Java 11!) * Press OK on the next dialog box