From 35cba5dfaef0486a10b7b8b683200bff25952b72 Mon Sep 17 00:00:00 2001 From: Ryan Lund Date: Mon, 20 Apr 2020 10:33:03 -0700 Subject: [PATCH] Dsptools examples (#457) * Add c test files for DSPTools example * Update tests Makefile to build DSPTools c tests * Add DSPTools example configs to ConfigMixins and RocketConfigs * Add dsptools and rocket-dsptools as dependancies for example * Add Scala implementations of DSPTools test blocks * Clean up GenericFIR scala * Modify dsptools blocks and mixins to match 'CanHave' when adding peripherial * Update documentation, will need reworking once FIR is characterized as fixed point * Update naming of Passthrough to Streaming Passthrough. Update naming of Thing to Chain and remove old Chain * Fix capitalization in docs (#419) * Add c test files for DSPTools example * Update tests Makefile to build DSPTools c tests * Add DSPTools example configs to ConfigMixins and RocketConfigs * Add dsptools and rocket-dsptools as dependancies for example * Add Scala implementations of DSPTools test blocks * Clean up GenericFIR scala * Modify dsptools blocks and mixins to match 'CanHave' when adding peripherial * Update documentation, will need reworking once FIR is characterized as fixed point * Update naming of Passthrough to Streaming Passthrough. Update naming of Thing to Chain and remove old Chain * Update docs/Customization/Dsptools-Blocks.rst Co-Authored-By: alonamid * Docummentation update for clarity and to explain how this can be applied to a generalized block * Some refactoring to get dsptools working with these examples * Oops, old files crept in Co-authored-by: Ryan Lund Co-authored-by: Sagar Karandikar Co-authored-by: alonamid Co-authored-by: Paul Rigge --- build.sbt | 15 +- docs/Customization/Dsptools-Blocks.rst | 105 ++++++++ docs/Customization/index.rst | 5 +- .../src/main/scala/config/RocketConfigs.scala | 12 + .../scala/example/dsptools/DspBlocks.scala | 146 ++++++++++++ .../scala/example/dsptools/GenericFIR.scala | 224 ++++++++++++++++++ .../scala/example/dsptools/Passthrough.scala | 156 ++++++++++++ tests/Makefile | 2 +- tests/fir.c | 51 ++++ tests/passthrough.c | 49 ++++ tools/chisel-testers | 2 +- tools/dsptools | 2 +- 12 files changed, 758 insertions(+), 11 deletions(-) create mode 100644 docs/Customization/Dsptools-Blocks.rst create mode 100644 generators/chipyard/src/main/scala/example/dsptools/DspBlocks.scala create mode 100644 generators/chipyard/src/main/scala/example/dsptools/GenericFIR.scala create mode 100644 generators/chipyard/src/main/scala/example/dsptools/Passthrough.scala create mode 100644 tests/fir.c create mode 100644 tests/passthrough.c diff --git a/build.sbt b/build.sbt index 05f05d7b..fdeed5a7 100644 --- a/build.sbt +++ b/build.sbt @@ -20,6 +20,8 @@ lazy val commonSettings = Seq( libraryDependencies += "com.github.scopt" %% "scopt" % "3.7.0", libraryDependencies += "org.scala-lang.modules" % "scala-jline" % "2.12.1", libraryDependencies += "com.typesafe.play" %% "play-json" % "2.6.10", + libraryDependencies += "org.typelevel" %% "spire" % "0.16.2", + libraryDependencies += "org.scalanlp" %% "breeze" % "1.0", addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full), unmanagedBase := (chipyardRoot / unmanagedBase).value, allDependencies := allDependencies.value.filterNot(_.organization == "edu.berkeley.cs"), @@ -129,6 +131,7 @@ lazy val iocell = (project in file("./tools/barstools/iocell/")) lazy val chipyard = conditionalDependsOn(project in file("generators/chipyard")) .dependsOn(boom, hwacha, sifive_blocks, sifive_cache, utilities, iocell, sha3, // On separate line to allow for cleaner tutorial-setup patches + dsptools, `rocket-dsptools`, gemmini, icenet, tracegen, ariane) .settings(commonSettings) @@ -175,19 +178,17 @@ lazy val barstoolsMacros = (project in file("./tools/barstools/macros/")) .enablePlugins(sbtassembly.AssemblyPlugin) .settings(commonSettings) -lazy val dsptools = (project in file("./tools/dsptools")) +lazy val dsptools = freshProject("dsptools", file("./tools/dsptools")) .dependsOn(chisel, chisel_testers) .settings( commonSettings, libraryDependencies ++= Seq( - "org.typelevel" %% "spire" % "0.14.1", - "org.scalanlp" %% "breeze" % "0.13.2", - "junit" % "junit" % "4.12" % "test", - "org.scalatest" %% "scalatest" % "3.0.5" % "test", - "org.scalacheck" %% "scalacheck" % "1.14.0" % "test" + "junit" % "junit" % "4.13" % "test", + "org.scalatest" %% "scalatest" % "3.0.8", + "org.scalacheck" %% "scalacheck" % "1.14.3" % "test" )) -lazy val `rocket-dsptools` = (project in file("./tools/dsptools/rocket")) +lazy val `rocket-dsptools` = freshProject("rocket-dsptools", file("./tools/dsptools/rocket")) .dependsOn(rocketchip, dsptools) .settings(commonSettings) diff --git a/docs/Customization/Dsptools-Blocks.rst b/docs/Customization/Dsptools-Blocks.rst new file mode 100644 index 00000000..228f5f91 --- /dev/null +++ b/docs/Customization/Dsptools-Blocks.rst @@ -0,0 +1,105 @@ +.. _dsptools-blocks: + +Dsptools Blocks +=============== + +Another way to create a MMIO peripheral is to use the Dsptools library for Chisel. In this method, a memory interface is created by creating a "chain". This chain consists of a custom module placed inside a ``DspBlock``, which is then sandwiched between a ``ReadQueue`` and ``WriteQueue``. Those queues then act as memory mapped interfaces to the Rocket Chip SoCs. This section will again primarily focus on designing Tilelink-based peripherals. However, through the resources provided in Dsptools, one could also define an AXI4-based peripheral by following similar steps. + +For this example, we will show you how to connect a simple FIR filter created using Dsptools as an MMIO peripheral. The full code can be found in ``generators/example/src/main/scala/dsptools/GenericFIR.scala``. That being said, one could substitute any module with a ready valid interface in the place of the FIR and achieve the same results. As long as the read and valid signals of the module are attached to those of a corresponding ``DSPBlock`` wrapper, and that wrapper is placed in a chain with a ``ReadQueue`` and a ``WriteQueue``, following the general outline establised by these steps will allow you to interact with that block as a memory mapped IO + +The module ``GenericFIR`` is the overall wrapper of our FIR module. This module links together a variable number of ``GenericFIRDirectCell`` submodules, each of which performs the computations for one coefficient in a FIR direct form architecture. It is important to note that both modules are type generic, which means that they can be instantiated for any datatype that implements ``Ring`` operations per the specifications on ``T``. + +.. literalinclude:: ../../generators/example/src/main/scala/dsptools/GenericFIR.scala + :language: scala + :start-after: DOC include start: GenericFIR chisel + :end-before: DOC include end: GenericFIR chisel + +.. literalinclude:: ../../generators/example/src/main/scala/dsptools/GenericFIR.scala + :language: scala + :start-after: DOC include start: GenericFIRDirectCell chisel + :end-before: DOC include end: GenericFIRDirectCell chisel + +Creating a DspBlock Extension +----------------------------- + +The first step in attaching the FIR filter as a MMIO peripheral is to create an abstract extension of ``DspBlock`` the wraps around the ``GenericFIR`` module. The main steps of this process are as follows. + +1. Instantiate a ``GenericFIR`` within ``GenericFIRBlock``. +2. Attach the ready and valid signals from the in and out connections. +3. Cast the module input data to the input type of ``GenericFIR`` (``GenericFIRBundle``) and attach. +4. Cast the output of ``GenericFIR`` to ``UInt`` and attach to the module output. + +.. literalinclude:: ../../generators/example/src/main/scala/dsptools/GenericFIR.scala + :language: scala + :start-after: DOC include start: GenericFIRBlock chisel + :end-before: DOC include end: GenericFIRBlock chisel + +Connecting by TileLink +---------------------- +With these classes implemented, you can begin to construct the chain by extending ``GenericFIRBlock`` while using the ``TLDspBlock`` trait via mixin. + +.. literalinclude:: ../../generators/example/src/main/scala/dsptools/GenericFIR.scala + :language: scala + :start-after: DOC include start: TLGenericFIRBlock chisel + :end-before: DOC include end: TLGenericFIRBlock chisel + +We can then construct the final chain by utilizing the ``TLWriteQueue`` and ``TLReadeQueue`` modules found in ``generators/example/src/main/scala/dsptools/DspBlocks.scala``. Inside our chain, we construct an instance of each queue as well as our ``TLGenericFIRBlock``. We then take the ``steamnode`` from each module and wire them all together to link the chain. + +.. literalinclude:: ../../generators/example/src/main/scala/dsptools/GenericFIR.scala + :language: scala + :start-after: DOC include start: TLGenericFIRChain chisel + :end-before: DOC include end: TLGenericFIRChain chisel + +Top Level Traits +---------------- +As in the previous MMIO example, we use a cake pattern to hook up our module to our SoC. + +.. literalinclude:: ../../generators/example/src/main/scala/dsptools/GenericFIR.scala + :language: scala + :start-after: DOC include start: CanHavePeripheryUIntTestFIR chisel + :end-before: DOC include end: CanHavePeripheryUIntTestFIR chisel + +Note that this is the point at which we decide the datatype for our FIR. It is also possible with some reworking to push the datatype selection out to the top level. + +Our module does not need to be connected to concrete IOs or wires, so we do not need to create a concrete trait. + +Constructing the Top and Config +------------------------------- + +Once again following the path of the previous MMIO example, we now want to mix our traits into the system as a whole. The code is from ``generators/example/src/main/scala/Top.scala`` + +.. literalinclude:: ../../generators/example/src/main/scala/Top.scala + :language: scala + :start-after: DOC include start: Top + :end-before: DOC include end: Top + +Finally, we create the configuration class in ``generators/example/src/main/scala/RocketConfigs.scala`` that uses the ``WithUIntTestFIR`` mixin defined in ``generators/example/src/main/scala/ConfigMixins.scala``. + +.. literalinclude:: ../../generators/example/src/main/scala/ConfigMixins.scala + :language: scala + :start-after: DOC include start: WithTestFIR + :end-before: DOC include end: WithTestFIR + +.. literalinclude:: ../../generators/example/src/main/scala/RocketConfigs.scala + :language: scala + :start-after: DOC include start: FIRRocketConfig + :end-before: DOC include end: FIRRocketConfig + +Testing +------- + +We can now test that the FIR is working. The test program is found in ``tests/gcd.c``. + +.. literalinclude:: ../../tests/fir.c + :language: c + +The test feed a series of values into the fir and compares the output to a golden model of computation. The base of the module's MMIO write region is at 0x2000 and the base of the read region is at 0x2100 by default. + +Compiling this program with ``make`` produces a ``fir.riscv`` executable. + +Now we can run our simulation. + +.. code-block:: shell + + cd sims/verilator + make CONFIG=GCDTLRocketConfig BINARY=../../tests/fir.riscv run-binary diff --git a/docs/Customization/index.rst b/docs/Customization/index.rst index 90d36fda..9421b79a 100644 --- a/docs/Customization/index.rst +++ b/docs/Customization/index.rst @@ -11,7 +11,9 @@ These guides will walk you through customization of your system-on-chip: - Adding custom MMIO widgets to the Chipyard memory system by Tilelink or AXI4, with custom Top-level IOs -- Standard practices for using keys, traits, and configs to parameterize your design +- Adding custom Dsptools based blocks as MMIO widgets. + +- Standard practices for using Keys, Traits, and Configs to parameterize your design - Customizing the memory hierarchy @@ -36,6 +38,7 @@ We recommend reading all these pages in order. Hit next to get started! RoCC-or-MMIO RoCC-Accelerators MMIO-Peripherals + Dsptools-Blocks Keys-Traits-Configs DMA-Devices Incorporating-Verilog-Blocks diff --git a/generators/chipyard/src/main/scala/config/RocketConfigs.scala b/generators/chipyard/src/main/scala/config/RocketConfigs.scala index e25680ca..c72fb1f5 100644 --- a/generators/chipyard/src/main/scala/config/RocketConfigs.scala +++ b/generators/chipyard/src/main/scala/config/RocketConfigs.scala @@ -384,3 +384,15 @@ class RingSystemBusRocketConfig extends Config( new freechips.rocketchip.subsystem.WithNBigCores(1) ++ new freechips.rocketchip.system.BaseConfig) // DOC include end: RingSystemBusRocket + +class UIntStreamingPassthroughRocketConfig extends Config( + new chipyard.example.WithUIntStreamingPassthrough ++ // use top with tilelink-controlled passthrough + new RocketConfig +) + +// DOC include start: FIRRocketConfig +class UIntTestFIRRocketConfig extends Config ( + new chipyard.example.WithUIntTestFIR ++ // use top with tilelink-controlled FIR + new RocketConfig +) +// DOC include end: FIRRocketConfig diff --git a/generators/chipyard/src/main/scala/example/dsptools/DspBlocks.scala b/generators/chipyard/src/main/scala/example/dsptools/DspBlocks.scala new file mode 100644 index 00000000..ad4c585e --- /dev/null +++ b/generators/chipyard/src/main/scala/example/dsptools/DspBlocks.scala @@ -0,0 +1,146 @@ +package chipyard.example + +import chisel3._ +import chisel3.util._ +import dspblocks._ +import dsptools.numbers._ +import freechips.rocketchip.amba.axi4stream._ +import freechips.rocketchip.config.Parameters +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.regmapper._ +import freechips.rocketchip.tilelink._ +import freechips.rocketchip.subsystem._ + +/** + * The memory interface writes entries into the queue. + * They stream out the streaming interface + * @param depth number of entries in the queue + * @param streamParameters parameters for the stream node + * @param p + */ +abstract class WriteQueue +( + val depth: Int = 8, + val streamParameters: AXI4StreamMasterParameters = AXI4StreamMasterParameters() +)(implicit p: Parameters) extends LazyModule with HasCSR { + // stream node, output only + val streamNode = AXI4StreamMasterNode(streamParameters) + + lazy val module = new LazyModuleImp(this) { + require(streamNode.out.length == 1) + + // get the output bundle associated with the AXI4Stream node + val out = streamNode.out.head._1 + // width (in bits) of the output interface + val width = out.params.n * 8 + // instantiate a queue + val queue = Module(new Queue(UInt(out.params.dataBits.W), depth)) + // connect queue output to streaming output + out.valid := queue.io.deq.valid + out.bits.data := queue.io.deq.bits + // don't use last + out.bits.last := false.B + queue.io.deq.ready := out.ready + + regmap( + // each write adds an entry to the queue + 0x0 -> Seq(RegField.w(width, queue.io.enq)), + // read the number of entries in the queue + (width+7)/8 -> Seq(RegField.r(width, queue.io.count)), + ) + } +} + +/** + * TLDspBlock specialization of WriteQueue + * @param depth number of entries in the queue + * @param csrAddress address range for peripheral + * @param beatBytes beatBytes of TL interface + * @param p + */ +class TLWriteQueue +( + depth: Int = 8, + csrAddress: AddressSet = AddressSet(0x2000, 0xff), + beatBytes: Int = 8, +)(implicit p: Parameters) extends WriteQueue(depth) with TLHasCSR { + val devname = "tlQueueIn" + val devcompat = Seq("ucb-art", "dsptools") + val device = new SimpleDevice(devname, devcompat) { + override def describe(resources: ResourceBindings): Description = { + val Description(name, mapping) = super.describe(resources) + Description(name, mapping) + } + } + // make diplomatic TL node for regmap + override val mem = Some(TLRegisterNode(address = Seq(csrAddress), device = device, beatBytes = beatBytes)) +} + +/** + * The streaming interface adds elements into the queue. + * The memory interface can read elements out of the queue. + * @param depth number of entries in the queue + * @param streamParameters parameters for the stream node + * @param p + */ +abstract class ReadQueue +( + val depth: Int = 8, + val streamParameters: AXI4StreamSlaveParameters = AXI4StreamSlaveParameters() +)(implicit p: Parameters) extends LazyModule with HasCSR { + val streamNode = AXI4StreamSlaveNode(streamParameters) + + lazy val module = new LazyModuleImp(this) { + require(streamNode.in.length == 1) + + // get the input associated with the stream node + val in = streamNode.in.head._1 + // make a Decoupled[UInt] that RegReadFn can do something with + val out = Wire(Decoupled(UInt())) + // get width of streaming input interface + val width = in.params.n * 8 + // instantiate a queue + val queue = Module(new Queue(UInt(in.params.dataBits.W), depth)) + // connect input to the streaming interface + queue.io.enq.valid := in.valid + queue.io.enq.bits := in.bits.data + in.ready := queue.io.enq.ready + // connect output to wire + out.valid := queue.io.deq.valid + out.bits := queue.io.deq.bits + queue.io.deq.ready := out.ready + + regmap( + // map the output of the queue + 0x0 -> Seq(RegField.r(width, RegReadFn(out))), + // read the number of elements in the queue + (width+7)/8 -> Seq(RegField.r(width, queue.io.count)), + ) + } +} + +/** + * TLDspBlock specialization of ReadQueue + * @param depth number of entries in the queue + * @param csrAddress address range + * @param beatBytes beatBytes of TL interface + * @param p + */ +class TLReadQueue +( + depth: Int = 8, + csrAddress: AddressSet = AddressSet(0x2100, 0xff), + beatBytes: Int = 8 +)(implicit p: Parameters) extends ReadQueue(depth) with TLHasCSR { + val devname = "tlQueueOut" + val devcompat = Seq("ucb-art", "dsptools") + val device = new SimpleDevice(devname, devcompat) { + override def describe(resources: ResourceBindings): Description = { + val Description(name, mapping) = super.describe(resources) + Description(name, mapping) + } + } + // make diplomatic TL node for regmap + override val mem = Some(TLRegisterNode(address = Seq(csrAddress), device = device, beatBytes = beatBytes)) + +} diff --git a/generators/chipyard/src/main/scala/example/dsptools/GenericFIR.scala b/generators/chipyard/src/main/scala/example/dsptools/GenericFIR.scala new file mode 100644 index 00000000..5335eb42 --- /dev/null +++ b/generators/chipyard/src/main/scala/example/dsptools/GenericFIR.scala @@ -0,0 +1,224 @@ +//// See LICENSE for license details. +// +package chipyard.example + +import chisel3._ +import chisel3.{Bundle, Module} +import chisel3.util._ +import dspblocks._ +import dsptools.numbers._ +import freechips.rocketchip.amba.axi4stream._ +import freechips.rocketchip.config.{Parameters, Field, Config} +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.tilelink._ +import freechips.rocketchip.subsystem._ + +// FIR params +case class GenericFIRParams( + writeAddress: BigInt = 0x2000, + readAddress: BigInt = 0x2100, + depth: Int +) + +case object GenericFIRKey extends Field[Option[GenericFIRParams]](None) + +class GenericFIRCellBundle[T<:Data:Ring](genIn:T, genOut:T) extends Bundle { + val data: T = genIn.cloneType + val carry: T = genOut.cloneType + + override def cloneType: this.type = GenericFIRCellBundle(genIn, genOut).asInstanceOf[this.type] +} +object GenericFIRCellBundle { + def apply[T<:Data:Ring](genIn:T, genOut:T): GenericFIRCellBundle[T] = new GenericFIRCellBundle(genIn, genOut) +} + +class GenericFIRCellIO[T<:Data:Ring](genIn:T, genOut:T) extends Bundle { + val coeff = Input(genIn.cloneType) + val in = Flipped(Decoupled(GenericFIRCellBundle(genIn, genOut))) + val out = Decoupled(GenericFIRCellBundle(genIn, genOut)) +} +object GenericFIRCellIO { + def apply[T<:Data:Ring](genIn:T, genOut:T): GenericFIRCellIO[T] = new GenericFIRCellIO(genIn, genOut) +} + +class GenericFIRBundle[T<:Data:Ring](proto: T) extends Bundle { + val data: T = proto.cloneType + + override def cloneType: this.type = GenericFIRBundle(proto).asInstanceOf[this.type] +} +object GenericFIRBundle { + def apply[T<:Data:Ring](proto: T): GenericFIRBundle[T] = new GenericFIRBundle(proto) +} + +class GenericFIRIO[T<:Data:Ring](genIn:T, genOut:T) extends Bundle { + val in = Flipped(Decoupled(GenericFIRBundle(genIn))) + val out = Decoupled(GenericFIRBundle(genOut)) +} +object GenericFIRIO { + def apply[T<:Data:Ring](genIn:T, genOut:T): GenericFIRIO[T] = new GenericFIRIO(genIn, genOut) +} + +// A generic FIR filter +// DOC include start: GenericFIR chisel +class GenericFIR[T<:Data:Ring](genIn:T, genOut:T, coeffs: Seq[T]) extends Module { + val io = IO(GenericFIRIO(genIn, genOut)) + + // Construct a vector of genericFIRDirectCells + val directCells = Seq.fill(coeffs.length){ Module(new GenericFIRDirectCell(genIn, genOut)).io } + + // Construct the direct FIR chain + for ((cell, coeff) <- directCells.zip(coeffs)) { + cell.coeff := coeff + } + + // Connect input to first cell + directCells.head.in.bits.data := io.in.bits.data + directCells.head.in.bits.carry := Ring[T].zero + directCells.head.in.valid := io.in.valid + io.in.ready := directCells.head.in.ready + + // Connect adjacent cells + // Note that .tail() returns a collection that consists of all + // elements in the inital collection minus the first one. + // This means that we zip together directCells[0, n] and + // directCells[1, n]. However, since zip ignores unmatched elements, + // the resulting zip is (directCells[0], directCells[1]) ... + // (directCells[n-1], directCells[n]) + for ((current, next) <- directCells.zip(directCells.tail)) { + next.in.bits := current.out.bits + next.in.valid := current.out.valid + current.out.ready := next.in.ready + } + + // Connect output to last cell + io.out.bits.data := directCells.last.out.bits.carry + directCells.last.out.ready := io.out.ready + io.out.valid := directCells.last.out.valid + +} +// DOC include end: GenericFIR chisel + +// A generic FIR direct cell used to construct a larger direct FIR chain +// +// in ----- [z^-1]-- out +// | +// coeff ----[*] +// | +// carryIn --[+]-- carryOut +// +// DOC include start: GenericFIRDirectCell chisel +class GenericFIRDirectCell[T<:Data:Ring](genIn: T, genOut: T) extends Module { + val io = IO(GenericFIRCellIO(genIn, genOut)) + + // Registers to delay the input and the valid to propagate with calculations + val hasNewData = RegInit(0.U) + val inputReg = Reg(genIn.cloneType) + + // Passthrough ready + io.in.ready := io.out.ready + + // When a new transaction is ready on the input, we will have new data to output + // next cycle. Take this data in + when (io.in.fire()) { + hasNewData := 1.U + inputReg := io.in.bits.data + } + + // We should output data when our cell has new data to output and is ready to + // recieve new data. This insures that every cell in the chain passes its data + // on at the same time + io.out.valid := hasNewData & io.in.fire() + io.out.bits.data := inputReg + + // Compute carry + // This uses the ring implementation for + and *, i.e. + // (a * b) maps to (Ring[T].prod(a, b)) for whicever T you use + io.out.bits.carry := inputReg * io.coeff + io.in.bits.carry +} +// DOC include end: GenericFIRDirectCell chisel + + +// DOC include start: GenericFIRBlock chisel +abstract class GenericFIRBlock[D, U, EO, EI, B<:Data, T<:Data:Ring] +( + genIn: T, + genOut: T, + coeffs: Seq[T] +)(implicit p: Parameters) extends DspBlock[D, U, EO, EI, B] { + val streamNode = AXI4StreamIdentityNode() + val mem = None + + lazy val module = new LazyModuleImp(this) { + require(streamNode.in.length == 1) + require(streamNode.out.length == 1) + + val in = streamNode.in.head._1 + val out = streamNode.out.head._1 + + // instantiate generic fir + val fir = Module(new GenericFIR(genIn, genOut, coeffs)) + + // Attach ready and valid to outside interface + in.ready := fir.io.in.ready + fir.io.in.valid := in.valid + + fir.io.out.ready := out.ready + out.valid := fir.io.out.valid + + // cast UInt to T + fir.io.in.bits := in.bits.data.asTypeOf(GenericFIRBundle(genIn)) + + // cast T to UInt + out.bits.data := fir.io.out.bits.asUInt + } +} +// DOC include end: GenericFIRBlock chisel + +// DOC include start: TLGenericFIRBLock chisel +class TLGenericFIRBlock[T<:Data:Ring] +( + val genIn: T, + val genOut: T, + coeffs: Seq[T] +)(implicit p: Parameters) extends +GenericFIRBlock[TLClientPortParameters, TLManagerPortParameters, TLEdgeOut, TLEdgeIn, TLBundle, T]( + genIn, genOut, coeffs +) with TLDspBlock +// DOC include end: TLGenericFIRBlock chisel + +// DOC include start: TLGenericFIRChain chisel +class TLGenericFIRChain[T<:Data:Ring] (genIn: T, genOut: T, coeffs: Seq[T], params: GenericFIRParams)(implicit p: Parameters) + extends LazyModule { + val writeQueue = LazyModule(new TLWriteQueue(params.depth, AddressSet(params.writeAddress, 0xff))) + val fir = LazyModule(new TLGenericFIRBlock(genIn, genOut, coeffs)) + val readQueue = LazyModule(new TLReadQueue(params.depth, AddressSet(params.readAddress, 0xff))) + + // connect streamNodes of queues and FIR + readQueue.streamNode := fir.streamNode := writeQueue.streamNode + + lazy val module = new LazyModuleImp(this) +} +// DOC include end: TLGenericFIRChain chisel + +// DOC include start: CanHavePeripheryUIntTestFIR chisel +trait CanHavePeripheryUIntTestFIR extends BaseSubsystem { + val fir = p(GenericFIRKey) match { + case Some(params) => { + val fir = LazyModule(new TLGenericFIRChain(UInt(8.W), UInt(12.W), Seq(1.U, 2.U, 3.U), params)) + + pbus.toVariableWidthSlave(Some("firWrite")) { fir.writeQueue.mem.get } + pbus.toVariableWidthSlave(Some("firRead")) { fir.readQueue.mem.get } + } + case None => None + } +} +// DOC include end: CanHavePeripheryUIntTestFIR chisel + +/** + * Mixin to add FIR to rocket config + */ +// DOC include start: WithTestFIR +class WithUIntTestFIR extends Config((site, here, up) => { + case GenericFIRKey => Some(GenericFIRParams(depth = 8)) +}) +// DOC include end: WithTestFIR diff --git a/generators/chipyard/src/main/scala/example/dsptools/Passthrough.scala b/generators/chipyard/src/main/scala/example/dsptools/Passthrough.scala new file mode 100644 index 00000000..1d953b83 --- /dev/null +++ b/generators/chipyard/src/main/scala/example/dsptools/Passthrough.scala @@ -0,0 +1,156 @@ +//// See LICENSE for license details. +// +package chipyard.example + +import chisel3._ +import chisel3.{Bundle, Module} +import chisel3.util._ +import dspblocks._ +import dsptools.numbers._ +import freechips.rocketchip.amba.axi4stream._ +import freechips.rocketchip.config.{Parameters, Field, Config} +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.tilelink._ +import freechips.rocketchip.subsystem._ + +// Simple passthrough to use as testbed sanity check +// StreamingPassthrough params +case class StreamingPassthroughParams( + writeAddress: BigInt = 0x2000, + readAddress: BigInt = 0x2100, + depth: Int +) + +// StreamingPassthrough key +case object StreamingPassthroughKey extends Field[Option[StreamingPassthroughParams]](None) + +class StreamingPassthroughBundle[T<:Data:Ring](proto: T) extends Bundle { + val data: T = proto.cloneType + + override def cloneType: this.type = StreamingPassthroughBundle(proto).asInstanceOf[this.type] +} +object StreamingPassthroughBundle { + def apply[T<:Data:Ring](proto: T): StreamingPassthroughBundle[T] = new StreamingPassthroughBundle(proto) +} + +class StreamingPassthroughIO[T<:Data:Ring](proto: T) extends Bundle { + val in = Flipped(Decoupled(StreamingPassthroughBundle(proto))) + val out = Decoupled(StreamingPassthroughBundle(proto)) +} +object StreamingPassthroughIO { + def apply[T<:Data:Ring](proto: T): StreamingPassthroughIO[T] = new StreamingPassthroughIO(proto) +} + +class StreamingPassthrough[T<:Data:Ring](proto: T) extends Module { + val io = IO(StreamingPassthroughIO(proto)) + + io.in.ready := io.out.ready + io.out.bits.data := io.in.bits.data + io.out.valid := io.in.valid +} + +/** + * Make DspBlock wrapper for StreamingPassthrough + * @param cordicParams parameters for cordic + * @param ev$1 + * @param ev$2 + * @param ev$3 + * @param p + * @tparam D + * @tparam U + * @tparam EO + * @tparam EI + * @tparam B + * @tparam T Type parameter for passthrough, i.e. FixedPoint or DspReal + */ +abstract class StreamingPassthroughBlock[D, U, EO, EI, B<:Data, T<:Data:Ring] +( + proto: T +)(implicit p: Parameters) extends DspBlock[D, U, EO, EI, B] { + val streamNode = AXI4StreamIdentityNode() + val mem = None + + lazy val module = new LazyModuleImp(this) { + require(streamNode.in.length == 1) + require(streamNode.out.length == 1) + + val in = streamNode.in.head._1 + val out = streamNode.out.head._1 + + // instantiate passthrough + val passthrough = Module(new StreamingPassthrough(proto)) + + // Pass ready and valid from read queue to write queue + in.ready := passthrough.io.in.ready + passthrough.io.in.valid := in.valid + + // cast UInt to T + passthrough.io.in.bits := in.bits.data.asTypeOf(StreamingPassthroughBundle(proto)) + + passthrough.io.out.ready := out.ready + out.valid := passthrough.io.out.valid + + // cast T to UInt + out.bits.data := passthrough.io.out.bits.asUInt + } +} + +/** + * TLDspBlock specialization of StreamingPassthrough + * @param cordicParams parameters for passthrough + * @param ev$1 + * @param ev$2 + * @param ev$3 + * @param p + * @tparam T Type parameter for passthrough data type + */ +class TLStreamingPassthroughBlock[T<:Data:Ring] +( + val proto: T +)(implicit p: Parameters) extends +StreamingPassthroughBlock[TLClientPortParameters, TLManagerPortParameters, TLEdgeOut, TLEdgeIn, TLBundle, T](proto) +with TLDspBlock + +/** + * A chain of queues acting as our MMIOs with the passthrough module in between them. + * @param depth depth of queues + * @param ev$1 + * @param ev$2 + * @param ev$3 + * @param p + * @tparam T Type parameter for passthrough, i.e. FixedPoint or DspReal + */ +class TLStreamingPassthroughChain[T<:Data:Ring](params: StreamingPassthroughParams, proto: T)(implicit p: Parameters) + extends LazyModule { + // instantiate lazy modules + val writeQueue = LazyModule(new TLWriteQueue(params.depth, AddressSet(params.writeAddress, 0xff))) + val passthrough = LazyModule(new TLStreamingPassthroughBlock(proto)) + val readQueue = LazyModule(new TLReadQueue(params.depth, AddressSet(params.readAddress, 0xff))) + + // connect streamNodes of queues and passthrough + readQueue.streamNode := passthrough.streamNode := writeQueue.streamNode + + lazy val module = new LazyModuleImp(this) +} + +trait CanHavePeripheryUIntStreamingPassthrough { this: BaseSubsystem => + val passthrough = p(StreamingPassthroughKey) match { + case Some(params) => { + val passthrough = LazyModule(new TLStreamingPassthroughChain(params, UInt(32.W))) + + pbus.toVariableWidthSlave(Some("passthroughWrite")) { passthrough.writeQueue.mem.get } + pbus.toVariableWidthSlave(Some("passthroughRead")) { passthrough.readQueue.mem.get } + + Some(passthrough) + } + case None => None + } +} + +/** + * Mixin to add passthrough to rocket config + */ +class WithUIntStreamingPassthrough extends Config((site, here, up) => { + case StreamingPassthroughKey => Some(StreamingPassthroughParams(depth = 8)) +}) + diff --git a/tests/Makefile b/tests/Makefile index 6f247e70..6fff62ce 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -5,7 +5,7 @@ LDFLAGS= -static include libgloss.mk -PROGRAMS = pwm blkdev accum charcount nic-loopback big-blkdev pingd +PROGRAMS = pwm blkdev accum charcount nic-loopback big-blkdev pingd passthrough fir .DEFAULT_GOAL := default diff --git a/tests/fir.c b/tests/fir.c new file mode 100644 index 00000000..46313a35 --- /dev/null +++ b/tests/fir.c @@ -0,0 +1,51 @@ +#define PASSTHROUGH_WRITE 0x2000 +#define PASSTHROUGH_WRITE_COUNT 0x2008 +#define PASSTHROUGH_READ 0x2100 +#define PASSTHROUGH_READ_COUNT 0x2108 + +#include "mmio.h" + +#include +#include +#include +#include + +int main(void) +{ + printf("Starting writing\n"); + uint32_t num_tests = 15; + uint32_t test_vector[15] = {1, 2, 3, 4, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 2}; + + for (int i = 0; i < num_tests; i++) { + reg_write64(PASSTHROUGH_WRITE, test_vector[i]); + } + + printf("Done writing\n"); + uint32_t rcnt = reg_read32(PASSTHROUGH_READ_COUNT); + printf("Write count: %d\n", reg_read32(PASSTHROUGH_WRITE_COUNT)); + printf("Read count: %d\n", rcnt); + + int failed = 0; + if (rcnt != 0) { + for (int i = 0; i < num_tests - 3; i++) { + uint32_t res = reg_read32(PASSTHROUGH_READ); + uint32_t expected = 3*test_vector[i] + 2*test_vector[i+1] + test_vector[i+2]; + if (res == expected) { + printf("\n\nPass: Got %d Expected %d\n\n", res, expected); + } else { + failed = 1; + printf("\n\nFail: Got %d Expected %d\n\n", res, expected); + } + } + } else { + failed = 1; + } + + if (failed) { + printf("\n\nSome tests failed\n\n"); + } else { + printf("\n\nAll tests passed\n\n"); + } + + return 0; +} diff --git a/tests/passthrough.c b/tests/passthrough.c new file mode 100644 index 00000000..a25e367b --- /dev/null +++ b/tests/passthrough.c @@ -0,0 +1,49 @@ +#define PASSTHROUGH_WRITE 0x2000 +#define PASSTHROUGH_WRITE_COUNT 0x2008 +#define PASSTHROUGH_READ 0x2100 +#define PASSTHROUGH_READ_COUNT 0x2108 + +#include "mmio.h" + +#include +#include +#include +#include + +int main(void) +{ + printf("Starting writing\n"); + uint32_t test_vector[7] = {3, 2, 1, 0, -1, -2, -3} ; + for (int i = 0; i < 7; i++) { + reg_write64(PASSTHROUGH_WRITE, test_vector[i]); + } + + printf("Done writing\n"); + uint32_t rcnt = reg_read32(PASSTHROUGH_READ_COUNT); + printf("Write count: %d\n", reg_read32(PASSTHROUGH_WRITE_COUNT)); + printf("Read count: %d\n", rcnt); + + int failed = 0; + if (rcnt != 0) { + for (int i = 0; i < 7; i++) { + uint32_t res = reg_read32(PASSTHROUGH_READ); + uint32_t expected = test_vector[i]; + if (res == expected) { + printf("\n\nPass: Got %d Expected %d\n\n", res, test_vector[i]); + } else { + failed = 1; + printf("\n\nFail: Got %d Expected %d\n\n", res, test_vector[i]); + } + } + } else { + failed = 1; + } + + if (failed) { + printf("\n\nSome tests failed\n\n"); + } else { + printf("\n\nAll tests passed\n\n"); + } + + return 0; +} diff --git a/tools/chisel-testers b/tools/chisel-testers index f410c593..1aa906fe 160000 --- a/tools/chisel-testers +++ b/tools/chisel-testers @@ -1 +1 @@ -Subproject commit f410c59316e5c43bac96411889aba8c5ab9a8fc0 +Subproject commit 1aa906fe168eb5ddca705ec955b27cf5c8856e4d diff --git a/tools/dsptools b/tools/dsptools index 15145ab6..63ac4b94 160000 --- a/tools/dsptools +++ b/tools/dsptools @@ -1 +1 @@ -Subproject commit 15145ab6230f869676de7eb730b4267fff7b11e8 +Subproject commit 63ac4b941bc711a0c7efd48b6418c86756b403ad