/*******************************************************************
 * Author: Rahul Bera
 * Description: This is purely combinatorial circuit
 * Given two sets of partial Q-values read from each vault,
 * this circuit sums up all partial Q-values for a given feature
 * and compares a max across Q-values of all features
 * *****************************************************************/
package pythia

import chisel3._
import chisel3.util._

class QVCompare extends Module{
   val io = IO(new Bundle{
      val qv0_p0 = Input(UInt(16.W))
      val qv0_p1 = Input(UInt(16.W))
      val qv0_p2 = Input(UInt(16.W))
      val qv1_p0 = Input(UInt(16.W))
      val qv1_p1 = Input(UInt(16.W))
      val qv1_p2 = Input(UInt(16.W))
      val qv_out = Output(UInt(16.W))
   })

   io.qv_out := Mux(qv0 > qv1, qv0, qv1)
}

val qv0 = io.qv0_p0 + io.qv0_p1 + io.qv0_p2


/**********************************************************
 * Author: Rahul Bera
 * Description: Implements a valut of a QVStore.
 * Num of planes in a valut: 3
 * Each plane supports two read ports, and one write port
 * ********************************************************/
package pythia

import chisel3._
import chisel3.util._

class Vault extends Module {
   val io = IO(new Bundle{
      val re = Input(Bool())
      val rdrow0 = Input(Vec(3, UInt(7.W)))
      val rdcol0 = Input(UInt(4.W))

      val rddata1 = Output(Vec(3, UInt(16.W)))
      val we = Input(Bool())
      val wrrow = Input(Vec(3, UInt(7.W)))
      val wrcol = Input(UInt(4.W))
      val wrdata = Input(Vec(3, UInt(16.W)))
   })

   // constituent planes
   val plane0 = Module(new Plane())
   val plane1 = Module(new Plane())
   val plane2 = Module(new Plane())

   plane0.io.rdrow0 := io.rdrow0(0)
   plane0.io.rdcol0 := io.rdcol0
   plane0.io.rdrow1 := io.rdrow1(0)
   plane0.io.rdcol1 := io.rdcol1
   plane0.io.re     := io.re

   plane1.io.rdrow0 := io.rdrow0(1)
   plane1.io.rdcol0 := io.rdcol0
   plane1.io.rdrow1 := io.rdrow1(1)
   plane1.io.rdcol1 := io.rdcol1
   plane1.io.re     := io.re

   plane2.io.rdrow0 := io.rdrow0(2)
   plane2.io.rdcol0 := io.rdcol0
   plane2.io.rdrow1 := io.rdrow1(2)
   plane2.io.rdcol1 := io.rdcol1
   plane2.io.re     := io.re

   // Return a vector of read data
   io.rddata0(0) := plane0.io.rddata0
   io.rddata0(1) := plane1.io.rddata0
   io.rddata0(2) := plane2.io.rddata0
   io.rddata1(0) := plane0.io.rddata1
   io.rddata1(1) := plane1.io.rddata1
   io.rddata1(2) := plane2.io.rddata1

   plane0.io.wrrow  := io.wrrow(0)
   plane0.io.wrcol  := io.wrcol
   plane0.io.wrdata := io.wrdata(0)
   plane0.io.we     := io.we

   plane1.io.wrrow  := io.wrrow(1)
   plane1.io.wrcol  := io.wrcol
   plane1.io.wrdata := io.wrdata(1)
   plane1.io.we     := io.we

   plane2.io.wrrow  := io.wrrow(2)
   plane2.io.wrcol  := io.wrcol
   plane2.io.wrdata := io.wrdata(2)
   plane2.io.we     := io.we
}

      val rdrow1 = Input(Vec(3, UInt(7.W)))
      val rdcol1 = Input(UInt(4.W))


// See LICENSE.txt for license details.
package pythia

import chisel3._
import chisel3.iotesters.{Driver, TesterOptionsManager}
import utils.PythiaRunner

object Launcher {
  val tests = Map(
    "IndexGen" -> { (manager: TesterOptionsManager) =>
      Driver.execute(() => new IndexGen(iType = 1, plane_offset = 0xdead), manager) {
        (c) => new IndexGenTests(c)
      }
    },
    "MaxN" -> { (manager: TesterOptionsManager) =>
      Driver.execute(() => new MaxN(), manager) {
        (c) => new MaxNTests(c)
      }
    },
    "Plane" -> { (manager: TesterOptionsManager) =>
      Driver.execute(() => new Plane(), manager) {
        (c) => new PlaneTests(c)
      }
    },
    "Vault" -> { (manager: TesterOptionsManager) =>
      Driver.execute(() => new Vault(), manager) {
        (c) => new VaultTests(c)
      }
    },
    "MasterModule" -> { (manager: TesterOptionsManager) =>
      Driver.execute(() => new MasterModule(), manager) {
        (c) => new MasterModuleTests(c)
      }
    },
    "QVCompare" -> { (manager: TesterOptionsManager) =>
      Driver.execute(() => new QVCompare(), manager) {
        (c) => new QVCompareTests(c)
      }
    }
  )

  def main(args: Array[String]): Unit = {
    PythiaRunner("pythia", tests, args)
  }


package pythia

import chisel3.iotesters.{PeekPokeTester, Driver, ChiselFlatSpec}

class MasterModuleTests(c: MasterModule) extends PeekPokeTester(c) {

   def updateQVStore(pc:Int, offset:Int, delta_path:Int, action:Int, reward:Int) = {
      poke(c.io.uPC, pc)
      poke(c.io.uOffset, offset)
      poke(c.io.uDeltaPath, delta_path)
      poke(c.io.uAction, action)
      poke(c.io.uReward, reward)
      poke(c.io.sigUpdate, 1)

   def queryQVStore(pc:Int, offset:Int, delta_path:Int, action:Int) = {
      poke(c.io.qPC, pc)
      poke(c.io.qOffset, offset)
      poke(c.io.qDeltaPath, delta_path)
      poke(c.io.sigQuery, 1)
      step(8)
      expect(c.io.qAction, action)
      poke(c.io.sigQuery, 0)
      // step(1)
   }

   val pc = 0x7fff3028
   val delta_path = 0x00000219
   updateQVStore(pc, 5, delta_path, 2, 6)
   queryQVStore(pc, 5, delta_path, 2)
   // updateQVStore(pc, 5, delta_path, 2, 10)
   // queryQVStore(pc, 5, delta_path, 2)
}

class MasterModuleTester extends ChiselFlatSpec {
  behavior of "MasterModule"
  backends foreach {backend =>
    it should s"correctly add randomly generated numbers and show carry in $backend" in {
      Driver(() => new MasterModule, backend)((c) => new MasterModuleTests(c)) should be (true)
    }
  }
}

      step(3)
      poke(c.io.sigUpdate, 0)
      // step(1)


package pythia

import chisel3.iotesters.{PeekPokeTester, Driver, ChiselFlatSpec}

class MaxNTests(c: MaxN) extends PeekPokeTester(c) {
   poke(c.io.nums(0), 143)
   poke(c.io.nums(1), 540)
   poke(c.io.nums(2), 376)
   poke(c.io.ids(0), 4)
   poke(c.io.ids(1), 3)
   poke(c.io.ids(2), 9)
   step(1)
   expect(c.io.maxNum, 540)
   expect(c.io.maxId, 3)
}

class MaxNTester extends ChiselFlatSpec {
  behavior of "MaxN"

  backends foreach {backend =>
    it should s"correctly add randomly generated numbers and show carry in $backend" in {
      Driver(() => new MaxN, backend)((c) => new MaxNTests(c)) should be (true)
    }
  }


package pythia

import chisel3.iotesters.{PeekPokeTester, Driver, ChiselFlatSpec}

class PlaneTests(c: Plane) extends PeekPokeTester(c) {
  def PlaneRead(row0:Int, col0:Int, data0:Int, row1:Int, col1:Int, data1:Int){
    poke(c.io.rdrow0, row0)
    poke(c.io.rdcol0, col0)
    poke(c.io.rdrow1, row1)
    poke(c.io.rdcol1, col1)
    poke(c.io.re, 1)
    step(1)
    expect(c.io.rddata0, data0)
    expect(c.io.rddata1, data1)
  }
  def PlaneWrite(row:Int, col:Int, data:Int){
    poke(c.io.wrrow, row)
    poke(c.io.wrcol, col)
    poke(c.io.wrdata, data)
    poke(c.io.we, 1)
    step(1)
  }
  for(t <- 0 until 9){
    val row0 = rnd.nextInt(128)
    val col0 = rnd.nextInt(16)
    val data0 = rnd.nextInt(65536)
    val row1 = rnd.nextInt(128)
    val col1 = rnd.nextInt(16)
    val data1 = rnd.nextInt(65536)

    PlaneWrite(row1, col1, data1)
    PlaneRead(row0, col0, data0, row1, col1, data1)
  }
}

class PlaneTester extends ChiselFlatSpec {
  behavior of "Plane"
  backends foreach {backend =>
    it should s"correctly add randomly generated numbers and show carry in $backend" in {
      Driver(() => new Plane, backend)((c) => new PlaneTests(c)) should be (true)
    }
  }
}


package pythia

import chisel3.iotesters.{PeekPokeTester, Driver, ChiselFlatSpec}

class QVCompareTests(c: QVCompare) extends PeekPokeTester(c) {
   def test(qv0_p0:Int, qv0_p1:Int, qv0_p2:Int, qv1_p0:Int, qv1_p1:Int, qv1_p2:Int) = {
      poke(c.io.qv0_p0, qv0_p0)
      poke(c.io.qv0_p1, qv0_p1)
      poke(c.io.qv0_p2, qv0_p2)
      poke(c.io.qv1_p0, qv1_p0)
      poke(c.io.qv1_p1, qv1_p1)
      poke(c.io.qv1_p2, qv1_p2)
      step(1)
      val qv0 = qv0_p0 + qv0_p1 + qv0_p2
      val qv1 = qv1_p0 + qv1_p1 + qv1_p2

      expect(c.io.qv_out, max)
   }

   test(1,2,3, 1,2,9)
   test(1,2,5, 1,2,9)
   test(1,2,10, 1,2,9)
}

class QVCompareTester extends ChiselFlatSpec {
  behavior of "QVCompare"
  backends foreach {backend =>
    it should s"correctly add randomly generated numbers and show carry in $backend" in {
      Driver(() => new QVCompare, backend)((c) => new QVCompareTests(c)) should be (true)
    }
  }
}

val max = if (qv0 >= qv1) {qv0} else {qv1}


package pythia

import chisel3.iotesters.{PeekPokeTester, Driver, ChiselFlatSpec}

class VaultTests(c: Vault) extends PeekPokeTester(c) {
   def VaultRead(row0_p0:Int, row0_p1:Int, row0_p2:Int, col0:Int, data0:Int,
                  row1_p0:Int, row1_p1:Int, row1_p2:Int, col1:Int, data1:Int) = {
     poke(c.io.rdrow0(0), row0_p0)
     poke(c.io.rdrow0(1), row0_p1)
     poke(c.io.rdrow0(2), row0_p2)
     poke(c.io.rdcol0, col0)
     poke(c.io.rdrow1(0), row1_p0)
     poke(c.io.rdrow1(1), row1_p1)
     poke(c.io.rdrow1(2), row1_p2)
     poke(c.io.rdcol1, col1)
     poke(c.io.re, 1)
     step(1)
     expect(c.io.rddata0(0), data0)

     expect(c.io.rddata1(1), data1)
     expect(c.io.rddata1(2), data1)
   }
   def VaultWrite(row_p0:Int, row_p1:Int, row_p2:Int, col:Int, data:Int) = {
     poke(c.io.wrrow(0), row_p0)
     poke(c.io.wrrow(1), row_p1)
     poke(c.io.wrrow(2), row_p2)
     poke(c.io.wrcol, col)
     poke(c.io.wrdata(0), data)
     poke(c.io.wrdata(1), data)
     poke(c.io.wrdata(2), data)
     poke(c.io.we, 1)
     step(1)
   }
   def test(row0_p0:Int, row0_p1:Int, row0_p2:Int, col0:Int, data0:Int,
                  row1_p0:Int, row1_p1:Int, row1_p2:Int, col1:Int, data1:Int) = {
      VaultWrite(row0_p0, row0_p1, row0_p2, col0, data0)
      VaultWrite(row1_p0, row1_p1, row1_p2, col1, data1)
      VaultRead(row0_p0, row0_p1, row0_p2, col0, data0, row1_p0, row1_p1, row1_p2, col1, data1)
   }

   test(2,3,9,5,10, 4,6,7,1,18)
   test(3,4,12,5,7891, 4,9,78,32,2390)
   test(96,48,67,42,9056, 28,90,69,25,4863)
   test(40,83,23,12,7813, 104,39,85,7,12986)
   test(1,100,69,27,785, 5,111,51,19,2308)
}

class VaultTester extends ChiselFlatSpec {
  behavior of "Vault"
  backends foreach {backend =>
    it should s"correctly add randomly generated numbers and show carry in $backend" in {
      Driver(() => new Vault, backend)((c) => new VaultTests(c)) should be (true)
    }
  }
}

     expect(c.io.rddata0(1), data0)
     expect(c.io.rddata0(2), data0)


// See LICENSE.txt for license details.
package utils

import scala.collection.mutable.ArrayBuffer
import chisel3.iotesters._

object OptionsCopy {
  def apply(t: TesterOptionsManager): TesterOptionsManager = {
    new TesterOptionsManager {
      testerOptions = t.testerOptions.copy()
      interpreterOptions = t.interpreterOptions.copy()
      chiselOptions = t.chiselOptions.copy()
      firrtlOptions = t.firrtlOptions.copy()
      treadleOptions = t.treadleOptions.copy()
    }
  }
}

object PythiaRunner {
  def apply(section: String, pythiaMap: Map[String, TesterOptionsManager => Boolean], args: Array[String]): Unit = {
    var successful = 0
    val errors = new ArrayBuffer[String]

    val optionsManager = new TesterOptionsManager()
    optionsManager.doNotExitOnHelp()

    optionsManager.parse(args)

    val programArgs = optionsManager.commonOptions.programArgs

    if(programArgs.isEmpty) {
      println("Available Pythia modules")
      for(x <- pythiaMap.keys) {
        println(x)
      }
      println("all")
      System.exit(0)
    }

    val problemsToRun = if(programArgs.exists(x => x.toLowerCase() == "all")) {
      pythiaMap.keys
    }
    else {
      programArgs
    }

    for(testName <- problemsToRun) {
      pythiaMap.get(testName) match {
        case Some(test) =>
          println(s"Starting pythia module $testName")
          try {
            // Start with a (relatively) clean set of options.
            val testOptionsManager = OptionsCopy(optionsManager)
            testOptionsManager.setTopName(testName)
            testOptionsManager.setTargetDirName(s"test_run_dir/$section/$testName")
            if(test(testOptionsManager)) {
              successful += 1
            }
            else {
              errors += s"Pythia module $testName: test error occurred"
            }
          }
          catch {
            case exception: Exception =>
              exception.printStackTrace()
              errors += s"Pythia module $testName: exception ${exception.getMessage}"
            case t : Throwable =>

          }
        case _ =>
          errors += s"Bad pythia module name: $testName"
      }

    }
    if(successful > 0) {
      println(s"Pythia modules passing: $successful")
    }
    if(errors.nonEmpty) {
      println("=" * 80)
      println(s"Errors: ${errors.length}: in the following pythia modules")
      println(errors.mkString("\n"))
      println("=" * 80)
      System.exit(1)
    }
  }
}

errors += s"Pythia module $testName: throwable ${t.getMessage}"


package aha
package dma

/* Chisel Imports */
import chisel3._
import chisel3.experimental.ChiselEnum
import chisel3.util.{Decoupled, is, switch}

/* Project Imports */
import aha.dma.CmdBundle
import aha.dma.util.AXILiteIntf

/**
 * DMA Control Engine
 *
 * @constructor         constructs a DMA control engine with provided address
 *                      bus width, data bus width, transfer bundle for reader,
 *                      transfer bundle for writer, and the value for the ID
 *                      register in the register file
 * @tparam R            transfer command bundle type for reader
 * @tparam W            transfer command bundle type for writer
 * @param AddrWidth     the width of AXI address busses (AWADDR and ARADDR)
 * @param DataWidth     the width of AXI data busses (WDATA and RDATA)
 * @param RdCmd         the bundle to use for read transfer commands
 * @param WrCmd         the bundle to use for write transfer commands
 * @param RegFileName   the name of the register file type to instantiate
 * @param MagicID       the value to use for the ID register in the register file
 *
 * @note transfer addresses must be aligned to the data bus width (DataWidth)
 */
class Controller[R <: CmdBundle, W <: CmdBundle] (
    AddrWidth   : Int,
    DataWidth   : Int,
    RdCmd       : R,
    WrCmd       : W,
    RegFileName : String,
    MagicID     : Int = 0x5A5A5A5A ) extends RawModule {

    import Controller.State
    import Controller.State._

    // =========================================================================
    // I/O
    // =========================================================================

    // Clock and Reset
    val ACLK            = IO(Input(Clock()))
    val ARESETn         = IO(Input(Bool()))

    // Interrupt
    val Irq             = IO(Output(Bool()))

    // AXI-Lite Register Interface
    lazy val RegIntf    = IO(Flipped(reg_file.getRegFileIntf))

    // Read Command/Status Interfaces
    val RdCmdIntf       = IO(Decoupled(RdCmd))
    val RdStatIntf      = IO(Flipped(Decoupled(UInt(2.W))))

    // Write Command/Status Interfaces
    val WrCmdIntf       = IO(Decoupled(WrCmd))
    val WrStatIntf      = IO(Flipped(Decoupled(UInt(2.W))))

    // =========================================================================
    // Chisel Work-Around for Active-Low Reset
    // =========================================================================

    val reset           = (!ARESETn).asAsyncReset

    // =========================================================================
    // Internal Logic
    // =========================================================================

    val state           = withClockAndReset(ACLK, reset) { RegInit(sIDLE) }

    //
    // Control Signals (from RegFile)
    //
    val go_pulse        = Wire(Bool())
    val irq_enable      = Wire(Bool())
    val irq_clear_pulse = Wire(Bool())
    val src_addr        = Wire(UInt(32.W))
    val dst_addr        = Wire(UInt(32.W))
    val length          = Wire(UInt(32.W))

    //
    // Status Signals (to RegFile)
    //
    val busy            = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val irq_status      = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val stat_code       = withClockAndReset(ACLK, reset) { RegInit(0.U(2.W)) }

    //
    // Connect RegFile
    //
    val reg_file: RegFile = Module(RegFile.get(
                                RegFileName,
                                RegFileConfig(32, 32, MagicID)
                            ))

    reg_file.ACLK       := ACLK
    reg_file.ARESETn    := ARESETn
    reg_file.RegIntf    <> RegIntf
    reg_file.Busy       := busy
    reg_file.IRQStatus  := irq_status
    reg_file.StatCode   := stat_code
    go_pulse            := reg_file.GO_Pulse
    irq_enable          := reg_file.IRQEnable
    irq_clear_pulse     := reg_file.IRQClear_Pulse
    src_addr            := reg_file.SrcAddr
    dst_addr            := reg_file.DstAddr
    length              := reg_file.Length

    //
    // Instantiate Splitter for Read Engine
    //
    val rd_trans_cmd_ready          = Wire(Bool())
    val rd_trans_cmd_valid          = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val rd_trans_cmd_bits           = withClockAndReset(ACLK, reset) {
                                        RegInit(0.U.asTypeOf(new TransBundle(AddrWidth)))
                                      }
    val rd_trans_stat_ready         = Wire(Bool())
    val rd_trans_stat_valid         = Wire(Bool())
    val rd_trans_stat_bits          = Wire(UInt(2.W))

    val rd_splitter                 = Module(   new Splitter(
                                                    new TransBundle(AddrWidth),
                                                    new CmdBundle(AddrWidth),
                                                    AddrWidth,
                                                    DataWidth
                                                )
                                            )

    rd_splitter.ACLK                := ACLK
    rd_splitter.ARESETn             := ARESETn
    rd_trans_cmd_ready              := rd_splitter.TransCmdIntf.ready
    rd_splitter.TransCmdIntf.valid  := rd_trans_cmd_valid
    rd_splitter.TransCmdIntf.bits   := rd_trans_cmd_bits
    rd_splitter.TransStatIntf.ready := rd_trans_stat_ready
    rd_trans_stat_valid             := rd_splitter.TransStatIntf.valid
    rd_trans_stat_bits              := rd_splitter.TransStatIntf.bits
    rd_splitter.XferCmdIntf         <> RdCmdIntf
    rd_splitter.XferStatIntf        <> RdStatIntf

    //
    // Instantiate Splitter for Write Engine
    //
    val wr_trans_cmd_ready          = Wire(Bool())
    val wr_trans_cmd_valid          = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val wr_trans_cmd_bits           = withClockAndReset(ACLK, reset) {
                                        RegInit(0.U.asTypeOf(new TransBundle(AddrWidth)))
                                      }
    val wr_trans_stat_ready         = Wire(Bool())
    val wr_trans_stat_valid         = Wire(Bool())
    val wr_trans_stat_bits          = Wire(UInt(2.W))

    val wr_splitter                 = Module(   new Splitter(
                                                    new TransBundle(AddrWidth),
                                                    new CmdBundle(AddrWidth),
                                                    AddrWidth,
                                                    DataWidth
                                                )
                                            )

    wr_splitter.ACLK                := ACLK
    wr_splitter.ARESETn             := ARESETn
    wr_trans_cmd_ready              := wr_splitter.TransCmdIntf.ready
    wr_splitter.TransCmdIntf.valid  := wr_trans_cmd_valid
    wr_splitter.TransCmdIntf.bits   := wr_trans_cmd_bits
    wr_splitter.TransStatIntf.ready := wr_trans_stat_ready
    wr_trans_stat_valid             := wr_splitter.TransStatIntf.valid
    wr_trans_stat_bits              := wr_splitter.TransStatIntf.bits
    wr_splitter.XferCmdIntf         <> WrCmdIntf
    wr_splitter.XferStatIntf        <> WrStatIntf

    //
    // Internal Signals
    //

    val write_cmd_posted    = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val read_stat_posted    = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val write_stat_posted   = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val read_stat           = withClockAndReset(ACLK, reset) { RegInit(0.U(2.W))}
    val write_stat          = withClockAndReset(ACLK, reset) { RegInit(0.U(2.W))}
    val err_stat            = withClockAndReset(ACLK, reset) { RegInit(0.U(2.W))}

    withClockAndReset(ACLK, reset) {

        //
        // State Register Update Logic
        //
        switch (state) {
            is (sIDLE) {
                when (go_pulse && (length === 0.U)) {
                    state   := sSTAT
                }.elsewhen (go_pulse) {
                    state   := sPOST
                }
            } // sIDLE
            is (sPOST) {
                when (read_cmd_posted && write_cmd_posted) {
                    state   := sSTAT
                }
            } // sPOST
            is (sSTAT) {
                when (read_stat_posted && write_stat_posted) {
                    state   := sIDLE
                }
            } // sSTAT
        } // switch (state)

        //
        // Interrupt Logic
        //
        Irq     := irq_status && irq_enable

        //
        // Error Status Code
        //
        switch (state) {
            is (sIDLE) {
                when (go_pulse && (length === 0.U)) {
                    err_stat    := 2.U
                }.otherwise {
                    err_stat    := 0.U
                }
            } // sIDLE
        } // switch (state)

        //
        // Busy Signal
        // (Can be made un-registered to avoid 1 cycle delay if necessary)
        //
        busy    := state =/= sIDLE

        //
        // IRQ Status
        //
        when ((state === sSTAT) && read_stat_posted && write_stat_posted) {
            irq_status  := true.B
        }.elsewhen (irq_clear_pulse) {
            irq_status  := false.B
        }

        //
        // Transaction Status Code
        //
        switch (state) {
            is (sSTAT) {
                when (read_stat_posted && write_stat_posted) {
                    when (err_stat =/= 0.U) {
                        stat_code   := err_stat
                    }.elsewhen (read_stat =/= 0.U) {
                        stat_code   := read_stat
                    }.otherwise {
                        stat_code   := write_stat
                    }
                }
            } // sSTAT
        } // switch (state)

        //
        // Read Status
        // (read_stat and read_stat_posted)
        //
        switch (state) {
            is (sIDLE) {
                read_stat               := 0.U
                read_stat_posted        := false.B
            } // sIDLE
            is (sSTAT) {
                when (rd_trans_stat_valid || (err_stat =/= 0.U)) {
                    read_stat           := rd_trans_stat_bits
                    read_stat_posted    := true.B
                }
            } // sSTAT
        } // switch (state)

        rd_trans_stat_ready := state === sSTAT

        //
        // Write Status
        // (write_stat and write_stat_posted)
        //
        switch (state) {
            is (sIDLE) {
                write_stat               := 0.U
                write_stat_posted        := false.B
            } // sIDLE
            is (sSTAT) {
                when (wr_trans_stat_valid || (err_stat =/= 0.U)) {
                    write_stat           := wr_trans_stat_bits
                    write_stat_posted    := true.B
                }
            } // sSTAT
        } // switch (state)

        wr_trans_stat_ready := state === sSTAT

        //
        // Read Transaction Command
        //
        switch (state) {
            is (sIDLE) {
                read_cmd_posted                 := false.B
                when (go_pulse && (length =/= 0.U)) {
                    rd_trans_cmd_valid          := true.B
                    rd_trans_cmd_bits.NumBytes  := length
                    rd_trans_cmd_bits.Address   := src_addr
                }.otherwise {
                    rd_trans_cmd_valid          := false.B
                }
            } // sIDLE
            is (sPOST) {
                when (rd_trans_cmd_ready) {
                    read_cmd_posted             := true.B
                    rd_trans_cmd_valid          := false.B
                }
            } // sPOST
        } // switch (state)

        //
        // Write Transaction Command
        //
        switch (state) {
            is (sIDLE) {
                write_cmd_posted                := false.B
                when (go_pulse && (length =/= 0.U)) {
                    wr_trans_cmd_valid          := true.B
                    wr_trans_cmd_bits.NumBytes  := length
                    wr_trans_cmd_bits.Address   := dst_addr
                }.otherwise {
                    wr_trans_cmd_valid          := false.B
                }
            } // sIDLE
            is (sPOST) {
                when (wr_trans_cmd_ready) {
                    write_cmd_posted            := true.B
                    wr_trans_cmd_valid          := false.B
                }
            } // sPOST
        } // switch (state)

    } // withClockAndReset(ACLK, reset)

} // class Controller

/**
 * Provides the states for the [[Controller]] finite state machine
 */
object Controller {

    /**
     * States for Controller FSM
     */
    object State extends ChiselEnum {
        val sIDLE, sPOST, sSTAT = Value
    }
} // object Controller

val read_cmd_posted     = withClockAndReset(ACLK, reset) { RegInit(false.B) }


package aha
package dma

/* Chisel Imports */
import chisel3._
import chisel3.experimental.dataview._
import chisel3.util.Decoupled

/* Project Imports */
import aha.dma.CmdBundle
import aha.dma.util.{AXI4Intf, AXI4WrIntfView, AXI4RdIntfView, PeekQueue}

/**
 * AXI4 Data Mover
 *
 * @constructor         constructs a data mover with the provided AXI ID width,
 *                      address bus width, data bus width, the transfer bundle
 *                      for the reader, the transfer bundle for the
 *                      writer, and the depth of the store-and-forward fifo
 * @tparam R            transfer command bundle type for reader
 * @tparam W            transfer command bundle type for writer
 * @param IdWidth       the width of AXI ID signals (AWID, BID, ARID, and RID)
 * @param AddrWidth     the width of AXI address busses (AWADDR and ARADDR)
 * @param DataWidth     the width of AXI data busses (WDATA and RDATA)
 * @param RdCmd         the bundle to use for read transfer commands
 * @param WrCmd         the bundle to use for write transfer commands
 * @param FifoDepth     the depth of the store-and-forward fifo
 *
 * @note transfer addresses must be aligned to the data bus width (DataWidth)
 * @note transfers must not cross the [[MAX_BURST_BYTES]]
 */
class DataMover[R <: CmdBundle, W <: CmdBundle] (
    IdWidth     : Int,
    AddrWidth   : Int,
    DataWidth   : Int,
    RdCmd       : R,
    WrCmd       : W,
    FifoDepth   : Int ) extends RawModule {

    // =========================================================================
    // I/O
    // =========================================================================

    // Clock and Reset
    val ACLK            = IO(Input(Clock()))
    val ARESETn         = IO(Input(Bool()))

    // Reader Command/Stat Interfaces
    val RdCmdIntf       = IO(Flipped(Decoupled(RdCmd)))
    val RdStatIntf      = IO(Decoupled(UInt(2.W)))

    // Writer Command/Stat Interfaces
    val WrCmdIntf       = IO(Flipped(Decoupled(WrCmd)))
    val WrStatIntf      = IO(Decoupled(UInt(2.W)))

    // AXI4 Interface
    val M_AXI           = IO(new AXI4Intf(IdWidth, AddrWidth, DataWidth))

    // =========================================================================
    // Internal Logic
    // =========================================================================

    //
    // AXI4 Read and Write Views
    //
    val axi4RdView      = M_AXI.viewAs[AXI4RdIntfView]
    val axi4WrView      = M_AXI.viewAs[AXI4WrIntfView]

    //
    // Instantiate Reader, Writer, and PeekQueue
    //
    val reader          = Module(new Reader(IdWidth, AddrWidth, DataWidth, RdCmd))
    val writer          = Module(new Writer(IdWidth, AddrWidth, DataWidth, WrCmd))
    val peekQueue       = Module(new PeekQueue(UInt(DataWidth.W), FifoDepth))

    //
    // Reader Connections
    //
    reader.ACLK         := ACLK
    reader.ARESETn      := ARESETn
    reader.CmdIntf      <> RdCmdIntf
    reader.StatIntf     <> RdStatIntf
    reader.ReadIntf     <> axi4RdView

    //
    // Writer Connections
    //
    writer.ACLK         := ACLK
    writer.ARESETn      := ARESETn
    writer.CmdIntf      <> WrCmdIntf
    writer.StatIntf     <> WrStatIntf
    writer.WriteIntf    <> axi4WrView

    //
    // PeekQueue Connections

    peekQueue.EnqIntf   <> reader.DataIntf
    peekQueue.DeqIntf   <> writer.DataIntf
}


package aha
package dma

/* Chisel Imports */
import chisel3._
import chisel3.experimental.ChiselEnum
import chisel3.util.{Decoupled, is, isPow2, switch}

/* Project Imports */
import aha.dma.{CmdBundle, log2 , MAX_BURST_BYTES, MAX_BURST_LENGTH, TransBundle}

/**
 * Transaction Splitter (splits a transaction into transfers)
 *
 * @constructor         constructs a transaction splitter with the provided
 *                      bundle type for transaction, bundle type for transfers,
 *                      address bus width and data bus width
 * @param TransCmd      bundle type for transaction commands
 * @param XferCmd       bundle type for transfer commands
 * @param AddrWidth     the width of AXI address busses (AWADDR and ARADDR)
 * @param DataWidth     the width of AXI data busses (WDATA and RDATA)
 *
 * @note transfer addresses must be aligned to the data bus width (DataWidth)
 */
class Splitter[C1 <: TransBundle, C2 <: CmdBundle] (
    TransCmd    : C1,
    XferCmd     : C2,
    AddrWidth   : Int,
    DataWidth   : Int ) extends RawModule {

    import Splitter.State
    import Splitter.State._

    // =========================================================================
    // I/O
    // =========================================================================

    // Clock and Reset
    val ACLK            = IO(Input(Clock()))
    val ARESETn         = IO(Input(Bool()))

    // Transaction Cmd/Status Interface
    val TransCmdIntf    = IO(Flipped(Decoupled(TransCmd)))
    val TransStatIntf   = IO(Decoupled(UInt(2.W)))

    // Transfer Cmd/Status Interface
    val XferCmdIntf     = IO(Decoupled(XferCmd))
    val XferStatIntf    = IO(Flipped(Decoupled(UInt(2.W))))

    // =========================================================================
    // Chisel Work-Around for Active-Low Reset
    // =========================================================================

    val reset       = (!ARESETn).asAsyncReset

    // =========================================================================
    // Internal Logic
    // =========================================================================

    // Maximum Number of Bytes in a Transfer
    val n_lanes             = DataWidth / 8
    val max_link_bytes      = MAX_BURST_LENGTH * n_lanes
    val max_xfer_bytes      = if (max_link_bytes < MAX_BURST_BYTES)
                                max_link_bytes
                              else
                              MAX_BURST_BYTES
    val max_xfer_bytes_log  = log2(max_xfer_bytes)

    // max_xfer_bytes must be a power of 2
    require (isPow2(max_xfer_bytes))

    val cur_num_bytes   = withClockAndReset(ACLK, reset) { RegInit(0.U(32.W)) }
    val rem_bytes       = withClockAndReset(ACLK, reset) { RegInit(0.U(32.W)) }
    val bBytes          = max_xfer_bytes.U - TransCmdIntf.bits.Address(max_xfer_bytes_log - 1, 0)
    val cur_addr        = withClockAndReset(ACLK, reset) { RegInit(0.U(AddrWidth.W)) }
    val next_addr       = ((cur_addr >> max_xfer_bytes_log.U) + 1.U) << max_xfer_bytes_log.U
    val trans_status    = withClockAndReset(ACLK, reset) { RegInit(0.U(2.W)) }

    withClockAndReset(ACLK, reset) {
        //
        // State Register Update Logic
        //
        switch (state) {
            is (sIDLE) {
                when (TransCmdIntf.valid) {
                    state   := sSPLIT
                }
            } // sIDLE
            is (sSPLIT) {
                when (XferCmdIntf.ready) {
                    state   := sWAIT
                }
            } // sSPLIT
            is (sWAIT) {
                when (XferStatIntf.valid) {
                    when (rem_bytes > 0.U) {
                        state   := sSPLIT
                    }.otherwise {
                        state   := sSTAT
                    }
                }
            } // sWAIT
            is (sSTAT) {
                when (TransStatIntf.ready) {
                    state   := sIDLE
                }
            } // sSTAT
        } // switch(state)

        //
        // Transaction Address Splitting
        //
        switch (state) {
            is (sIDLE) {
                when (TransCmdIntf.valid) {
                    cur_addr    := TransCmdIntf.bits.Address
                }
            } // sIDLE
            is (sWAIT) {
                when (XferStatIntf.valid) {
                    cur_addr    := next_addr
                }
            }
        } // switch (state)

        //
        // Transaction Size (bytes count) Splitting
        //
        switch (state) {
            is (sIDLE) {
                when (TransCmdIntf.valid) {
                    when (bBytes <= TransCmdIntf.bits.NumBytes) {
                        cur_num_bytes   := bBytes
                        rem_bytes       := TransCmdIntf.bits.NumBytes - bBytes
                    }.otherwise {
                        cur_num_bytes   := TransCmdIntf.bits.NumBytes
                        rem_bytes       := 0.U
                    }
                }
            } // sIDLE
            is (sWAIT) {
                when (XferStatIntf.valid) {
                    when (rem_bytes > max_xfer_bytes.U) {
                        cur_num_bytes   := max_xfer_bytes.U
                        rem_bytes       := rem_bytes - max_xfer_bytes.U
                    }.otherwise {
                        cur_num_bytes   := rem_bytes
                        rem_bytes       := 0.U
                    }
                }
            } // sWAIT
        } // switch (state)

        //
        // Transaction Status
        //
        switch (state) {
            is (sIDLE) {
                trans_status    := 0.U
            } // sIDLE
            is (sWAIT) {
                when (XferStatIntf.valid && (TransStatIntf.bits === 0.U)) {
                    trans_status    := XferStatIntf.bits
                }
            }
        } // switch (state)

        TransStatIntf.bits          := trans_status

        //
        // Transaction Cmd/Status
        //
        TransCmdIntf.ready          := state === sIDLE
        TransStatIntf.valid         := state === sSTAT

        //
        // Transfer Status Ready
        //
        XferStatIntf.ready          := state === sWAIT

        //
        // Transfer Command
        //
        XferCmdIntf.valid           := state === sSPLIT
        XferCmdIntf.bits.NumBytes   := cur_num_bytes
        XferCmdIntf.bits.Address    := cur_addr

    } // withClockAndReset(ACLK, reset)

} // class Splitter

/**
 * Provides the states for the [[Splitter]] finite state machine
 */
object Splitter {

    /**
     * States for Splitter FSM
     */
    object State extends ChiselEnum {
        val sIDLE, sSPLIT, sWAIT, sSTAT = Value
    } // object State

} // object Splitter

val state           = withClockAndReset(ACLK, reset) { RegInit(sIDLE) }


package aha

/* Scala Imports */
import scala.math._

/* Chisel Imports */
import chisel3._

/**
 * Provides project-wide constants, transfer and transaction bundle types, and
 * some utility functions
 */
package object dma {

    // =========================================================================
    // AXI Parameters
    // =========================================================================

    /**
     * Maximum Number of Bytes in an AXI Burst
     */
    val MAX_BURST_BYTES  = 4096

    /**
     * Maximum Burst Length Supported in INCR Mode
     *  AXI4 = 256
     *  AXI3 = 16
     */
    val MAX_BURST_LENGTH = 256

    /**
     * Transfer command bundle
     *
     * @constructor     constructs a transfer command bundle with a specified
     *                  AXI address bus width
     * @param AddrWidth the width of AXI address busses (AWADDR and ARADDR)
     */
    class CmdBundle(AddrWidth: Int) extends Bundle {
        val NumBytes    = UInt(clog2(MAX_BURST_BYTES).W)
        val Address     = UInt(AddrWidth.W)
    }

    /**
     * Transaction command bundle
     *
     * @constructor     constructs a transaction command bundle with a specified
     *                  AXI address bus width
     * @param AddrWidth the width of AXI address busses (AWADDR and ARADDR)
     */

     *
     * @param x the number of which the clog2 is computed
     * @return the log2 of x rounded up
     */
    def clog2(x: Int) : Int = {
        require(x >= 0)
        ceil(log(x)/log(2)).toInt
    }

    /**
     * Returns log2 of a number
     *
     * @param x the number of which the log2 is computed
     * @return the log2 of x
     */
    def log2(x: Int) : Int = {
        require(x >= 0)
        (log(x)/log(2)).toInt
    }
}

    class TransBundle(AddrWidth: Int) extends Bundle {
        val NumBytes    = UInt(32.W)
        val Address     = UInt(AddrWidth.W)
    }


package aha
package dma

/* Chisel Imports */
import chisel3._
import chisel3.experimental.ChiselEnum
import chisel3.util.{Decoupled, is, switch}

/* Project Imports */
import aha.dma.{CmdBundle, log2}
import aha.dma.util.{AXI4RdIntfView, AXI4AddrPayload}

/**
 * AXI4 Read Engine
 *
 * @constructor         constructs a AXI4 read engine with an AXI ID width,
 *                      address bus width, data bus width, and the type of a
 *                      transfer command bundle
 * @tparam R            transfer command bundle type for reader
 * @param IdWidth       the width of AXI ID signals (AWID, BID, ARID, and RID)
 * @param AddrWidth     the width of AXI address busses (AWADDR and ARADDR)
 * @param DataWidth     the width of AXI data busses (WDATA and RDATA)
 * @param Cmd           the bundle to use for transfer commands
 *
 * @note transfer addresses must be aligned to the data bus width (DataWidth)
 * @note transfers must not cross the [[MAX_BURST_BYTES]]
 */
class Reader[R <: CmdBundle](IdWidth: Int,
                             AddrWidth: Int,
                             DataWidth: Int,
                             Cmd : R) extends RawModule {

    import Reader.State
    import Reader.State._

    // =========================================================================
    // I/O
    // =========================================================================

    // Clock and Reset
    val ACLK        = IO(Input(Clock()))
    val ARESETn     = IO(Input(Bool()))

    // Command/Stat Interfaces
    val CmdIntf     = IO(Flipped(Decoupled(Cmd)))
    val StatIntf    = IO(Decoupled(UInt(2.W)))

    // Data Packet Interface
    val DataIntf    = IO(Decoupled(UInt(DataWidth.W)))

    // AXI4 Read Interface
    val ReadIntf    = IO(new AXI4RdIntfView(IdWidth, AddrWidth, DataWidth))

    // =========================================================================
    // Chisel Work-Around for Active-Low Reset
    // =========================================================================

    val reset       = (!ARESETn).asAsyncReset

    // =========================================================================
    // Internal Logic
    // =========================================================================

    val last_beat   = Wire(Bool())
    val read_resp   = withClockAndReset(ACLK, reset) { RegInit(0.U(2.W)) }

    // AR Channel Signals
    val ar_size     = log2(DataWidth/8)
    val ar_addr     = (CmdIntf.bits.Address >> ar_size.U) << ar_size.U
    val ar_len      = Wire(UInt(8.W))
    val ar_valid    = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val ar_payload  = withClockAndReset(ACLK, reset) {
                        RegInit(0.U.asTypeOf(new AXI4AddrPayload(IdWidth, AddrWidth)))
                      }

    // R Channel Signal
    val r_ready     = Wire(Bool())

    withClockAndReset(ACLK, reset) {
        //
        // State Register Update Logic
        //
        switch (state) {
            is (sIDLE) {
                when (CmdIntf.valid) {
                    state   := sADDR
                }
            } // sIDLE
            is (sADDR) {
                when (ReadIntf.AR.ready) {
                    state   := sDATA
                }
            } // sADDR
            is (sDATA) {
                when (last_beat) {
                    state   := sSTAT
                }
            } // sDATA
            is (sSTAT) {
                when (StatIntf.ready) {
                    state   := sIDLE
                }
            } // sSTAT
        } // switch(state)

        //
        // AR Channel Signals
        //
        switch (state) {
            is (sIDLE) {
                when (CmdIntf.valid) {
                    ar_valid            := true.B
                    ar_payload.ID       := 0.U
                    ar_payload.ADDR     := ar_addr
                    ar_payload.LEN      := ar_len
                    ar_payload.SIZE     := ar_size.U
                    ar_payload.BURST    := 1.U
                    ar_payload.LOCK     := false.B
                    ar_payload.CACHE    := 2.U
                    ar_payload.PROT     := 2.U
                }.otherwise {
                    ar_valid            := false.B
                }
            } // sIDLE
            is (sADDR) {
                when (ReadIntf.AR.ready) {
                    ar_valid            := false.B
                }
            } // sADDR
        } // switch(state)

        ReadIntf.AR.valid   := ar_valid
        ReadIntf.AR.bits    := ar_payload

        //
        // Internal Read Response Accumulation
        // (read errors are sticky)
        //
        switch (state) {
            is (sIDLE) {
                read_resp   := 0.U
            } // sIDLE
            is (sDATA) {
                when (ReadIntf.R.valid && ReadIntf.R.ready) {
                    when (read_resp === 0.U) {
                        read_resp   := ReadIntf.R.bits.RESP
                    }
                }
            } // sDATA
        } // switch(state)

        //
        // RREADY
        //
        when (state === sDATA) {
            r_ready     := DataIntf.ready
        }.otherwise {
            r_ready     := false.B
        }

        ReadIntf.R.ready := r_ready

        //
        // Internal Signals
        //
        last_beat   := ReadIntf.R.valid && ReadIntf.R.ready && ReadIntf.R.bits.LAST
        ar_len      := ((CmdIntf.bits.NumBytes >> ar_size.U) +
                         CmdIntf.bits.NumBytes(ar_size-1, 0).orR) - 1.U

        //
        // Status Signals
        //
        StatIntf.valid  := state === sSTAT
        StatIntf.bits   := read_resp

        //
        // Data Interface Signals
        //
        DataIntf.valid  := (state === sDATA) && ReadIntf.R.valid
        DataIntf.bits   := ReadIntf.R.bits.DATA

        //
        // Command Interface Ready
        //
        CmdIntf.ready   := state === sIDLE

    } // withClockAndReset(ACLK, reset)

} // class Reader

/**
 * Provides the states for the [[Reader]] finite state machine
 */
object Reader {

    /**
     * States for Reader FSM
     */
    object State extends ChiselEnum {
        val sIDLE, sADDR, sDATA, sSTAT = Value
    } // object State
} // object Reader

val state       = withClockAndReset(ACLK, reset) { RegInit(sIDLE) }


package aha
package dma

/* Chisel Imports */
import chisel3._
import chisel3.util.{Cat, is, switch}

/* Project Imports */
import aha.dma.util.APBIntf

/**
 * DMA Register File with APB interface
 *
 * @constructor         constructs a register file with the provided value for
 *                      the ID register
 * @param MagicID       the value for the ID register
 */
class APBRegFile(val MagicID: Int = 0x5A5A5A5A) extends RegFile {

    // Type of Register File Interface
    type RegIntfTy      = APBIntf

    // AXI-Lite Interface
    def getRegFileIntf  = new APBIntf(32, 32)

    // =========================================================================
    // Chisel Work-Around for Active-Low Reset
    // =========================================================================

    val reset           = (!ARESETn).asAsyncReset

    // =========================================================================
    // Internal Logic
    // =========================================================================

    //
    // Registers
    //
    val reg_go_pulse        = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val reg_ie              = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val reg_intclr_pulse    = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val reg_src_addr        = withClockAndReset(ACLK, reset) { RegInit(0.U(32.W)) }
    val reg_dst_addr        = withClockAndReset(ACLK, reset) { RegInit(0.U(32.W)) }
    val reg_length          = withClockAndReset(ACLK, reset) { RegInit(0.U(32.W)) }

    //
    // Internal Signals
    //
    val prdata              = withClockAndReset(ACLK, reset) { RegInit(0.U(32.W)) }
    val pready              = Wire(Bool())
    val pslverr             = withClockAndReset(ACLK, reset) { RegInit(false.B) }

    val reg_data_out        = Wire(UInt(32.W))
    val setup_phase         = Wire(Bool())
    val wr_en               = Wire(Bool())
    val rd_en               = Wire(Bool())

    setup_phase             := RegIntf.PSEL && ~RegIntf.PENABLE
    wr_en                   := setup_phase && RegIntf.PWRITE
    rd_en                   := setup_phase && ~RegIntf.PWRITE

    //
    // APB Connections
    //
    RegIntf.PRDATA          := prdata
    RegIntf.PREADY          := pready
    RegIntf.PSLVERR         := pslverr

    withClockAndReset(ACLK, reset) {

        //
        // Generate GO Pulse
        //
        when (wr_en && (RegIntf.PADDR(4, 2) === 0.U)) {
            reg_go_pulse    := RegIntf.PWDATA(0).asBool
        }.otherwise {
            reg_go_pulse    := false.B
        }

        GO_Pulse    := reg_go_pulse

        //
        // Generate Interrupt Clear Pulse
        //
        when (wr_en && (RegIntf.PADDR(4, 2) === 2.U)) {
            reg_intclr_pulse    := RegIntf.PWDATA(1).asBool
        }.otherwise {
            reg_intclr_pulse    := false.B
        }

        IRQClear_Pulse  := reg_intclr_pulse

        //
        // Interrupt Enable Register Update Logic
        //
        when (wr_en && (RegIntf.PADDR(4, 2) === 0.U)) {
            reg_ie  := RegIntf.PWDATA(1).asBool
        }

        IRQEnable   := reg_ie

        //
        // Source Address Register
        //
        when (wr_en && (RegIntf.PADDR(4, 2) === 3.U)) {
            reg_src_addr  := RegIntf.PWDATA
        }

        SrcAddr := reg_src_addr

        //
        // Destination Address Register
        //
        when (wr_en && (RegIntf.PADDR(4, 2) === 4.U)) {
            reg_dst_addr  := RegIntf.PWDATA
        }

        DstAddr := reg_dst_addr

        //
        // Length Register
        //
        when (wr_en && (RegIntf.PADDR(4, 2) === 5.U)) {
            reg_length  := RegIntf.PWDATA
        }

        Length := reg_length

        //
        // Output Data Select Logic
        //
        reg_data_out    := 0.U
        switch (RegIntf.PADDR(4, 2)) {
            is (0.U) { reg_data_out := Cat(reg_ie, 0.U(1.W)) }
            is (1.U) { reg_data_out := Cat(StatCode, IRQStatus.asUInt, Busy.asUInt) }
            is (3.U) { reg_data_out := reg_src_addr }

            is (5.U) { reg_data_out := reg_length }
            is (6.U) { reg_data_out := MagicID.U }
        }

        //
        // prdata
        //
        when (rd_en) {
            prdata   := reg_data_out
        }

        //
        // pready
        //
        pready  := true.B // Always Ready

        //
        // pslverr
        //
        when (rd_en) {
            when (RegIntf.PADDR(4, 2) > 6.U) {
                pslverr := true.B
            }.otherwise {
                pslverr := false.B
            }
        }.otherwise {
            pslverr := false.B
        }

    } // withClockAndReset(ACLK, reset)
} // class RegFile

is (4.U) { reg_data_out := reg_dst_addr }


package aha
package dma

/* Chisel Imports */
import chisel3._
import chisel3.util.{Cat, is, switch}

/* Project Imports */
import aha.dma.util.AXILiteIntf

/**
 * DMA Register File with AXI-Lite interface
 *
 * @constructor         constructs a register file with the provided value for
 *                      the ID register
 * @param MagicID       the value for the ID register
 */
class AXILiteRegFile(val MagicID: Int = 0x5A5A5A5A) extends RegFile {

    // Type of Register File Interface
    type RegIntfTy      = AXILiteIntf

    // AXI-Lite Interface
    def getRegFileIntf = new AXILiteIntf(32, 32)

    // =========================================================================
    // Chisel Work-Around for Active-Low Reset
    // =========================================================================

    val reset           = (!ARESETn).asAsyncReset

    // =========================================================================
    // Internal Logic
    // =========================================================================

    //
    // Registers
    //
    val reg_go_pulse        = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val reg_ie              = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val reg_intclr_pulse    = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val reg_src_addr        = withClockAndReset(ACLK, reset) { RegInit(0.U(32.W)) }
    val reg_dst_addr        = withClockAndReset(ACLK, reset) { RegInit(0.U(32.W)) }
    val reg_length          = withClockAndReset(ACLK, reset) { RegInit(0.U(32.W)) }

    //
    // Internal Signals
    //
    val axi_awaddr          = withClockAndReset(ACLK, reset) { RegInit(0.U(32.W)) }
    val axi_awready         = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val aw_en               = withClockAndReset(ACLK, reset) { RegInit(true.B) }
    val axi_wready          = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val axi_bvalid          = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val axi_bresp           = withClockAndReset(ACLK, reset) { RegInit(0.U(2.W)) }
    val axi_araddr          = withClockAndReset(ACLK, reset) { RegInit(0.U(32.W)) }
    val axi_arready         = withClockAndReset(ACLK, reset) { RegInit(false.B) }
    val axi_rdata           = withClockAndReset(ACLK, reset) { RegInit(0.U(32.W)) }
    val axi_rresp           = withClockAndReset(ACLK, reset) { RegInit(0.U(2.W)) }
    val axi_rvalid          = withClockAndReset(ACLK, reset) { RegInit(false.B) }

    val slv_reg_rden        = Wire(Bool())
    val slv_reg_wren        = Wire(Bool())
    val reg_data_out        = Wire(UInt(32.W))

    //
    // AXI-Lite Connections
    //
    RegIntf.AWREADY         := axi_awready
    RegIntf.WREADY          := axi_wready
    RegIntf.BRESP           := axi_bresp
    RegIntf.BVALID          := axi_bvalid
    RegIntf.ARREADY         := axi_arready
    RegIntf.RDATA           := axi_rdata
    RegIntf.RRESP           := axi_rresp
    RegIntf.RVALID          := axi_rvalid

    withClockAndReset(ACLK, reset) {

        //
        // axi_awready and aw_en
        //
        when (~axi_awready && RegIntf.AWVALID && RegIntf.WVALID && aw_en) {
            axi_awready := true.B
            aw_en       := false.B
        }.elsewhen (RegIntf.BREADY && axi_bvalid) {
            axi_awready := false.B
            aw_en       := true.B
        }.otherwise {
            axi_awready := false.B
        }

        //
        // axi_awaddr
        //
        when (~axi_awready && RegIntf.AWVALID && RegIntf.WVALID && aw_en) {
            axi_awaddr  := RegIntf.AWADDR
        }

        //
        // axi_wready
        //
        when (~axi_wready && RegIntf.WVALID && RegIntf.AWVALID && aw_en) {
            axi_wready  := true.B
        }.otherwise {
            axi_wready  := false.B
        }

        //
        // axi_bvalid and axi_bresp
        //
        when (axi_awready && RegIntf.AWVALID && ~axi_bvalid && axi_wready && RegIntf.WVALID) {
            axi_bvalid  := true.B
            axi_bresp   := 0.U
        }.elsewhen (RegIntf.BREADY && axi_bvalid) {
            axi_bvalid  := false.B
        }

        //
        // Write Enable Signal
        //
        slv_reg_wren    := axi_awready && RegIntf.AWVALID && axi_wready && RegIntf.WVALID

        //
        // Generate GO Pulse
        //
        when (slv_reg_wren && (axi_awaddr(4, 2) === 0.U) && (RegIntf.WSTRB(0) === 1.U)) {
            reg_go_pulse    := RegIntf.WDATA(0).asBool
        }.otherwise {
            reg_go_pulse    := false.B
        }

        GO_Pulse    := reg_go_pulse

        //
        // Generate Interrupt Clear Pulse
        //
        when (slv_reg_wren && (axi_awaddr(4, 2) === 2.U) && (RegIntf.WSTRB(0) === 1.U)) {
            reg_intclr_pulse    := RegIntf.WDATA(1).asBool
        }.otherwise {
            reg_intclr_pulse    := false.B
        }

        IRQClear_Pulse  := reg_intclr_pulse

        //
        // Interrupt Enable Register Update Logic
        //
        when (slv_reg_wren && (axi_awaddr(4, 2) === 0.U) && (RegIntf.WSTRB(0) === 1.U)) {
            reg_ie  := RegIntf.WDATA(1).asBool
        }

        IRQEnable   := reg_ie

        //
        // Source Address Register
        //
        val reg_src_addr_v = Wire(Vec(4, UInt(8.W)))
        reg_src_addr_v := reg_src_addr.asTypeOf(Vec(4, UInt(8.W)))

        when (slv_reg_wren && (axi_awaddr(4, 2) === 3.U)) {

        //
        // Destination Address Register
        //
        val reg_dst_addr_v = Wire(Vec(4, UInt(8.W)))
        reg_dst_addr_v := reg_dst_addr.asTypeOf(Vec(4, UInt(8.W)))

        when (slv_reg_wren && (axi_awaddr(4, 2) === 4.U)) {
            for (n <- 0 to 3) {
                when (RegIntf.WSTRB(n) === 1.U) {
                    reg_dst_addr_v(n)   := RegIntf.WDATA(n*8+7, n*8)
                }
            }
            reg_dst_addr    := reg_dst_addr_v.asTypeOf(UInt(32.W))
        }

        DstAddr := reg_dst_addr

        //
        // Length Register
        //
        val reg_length_v    = Wire(Vec(4, UInt(8.W)))
        reg_length_v        := reg_length.asTypeOf(Vec(4, UInt(8.W)))

        when (slv_reg_wren && (axi_awaddr(4, 2) === 5.U)) {
            for (n <- 0 to 3) {
                when (RegIntf.WSTRB(n) === 1.U) {
                    reg_length_v(n)   := RegIntf.WDATA(n*8+7, n*8)
                }
            }
            reg_length    := reg_length_v.asTypeOf(UInt(32.W))
        }

        Length  := reg_length

        //
        // axi_arready and axi_araddr
        //
        when (~axi_arready && RegIntf.ARVALID) {
            axi_arready := true.B
            axi_araddr  := RegIntf.ARADDR
        }.otherwise {
            axi_arready := false.B
        }

        //
        // axi_rvalid and axi_rresp
        //
        when (axi_arready && RegIntf.ARVALID && ~axi_rvalid) {
            axi_rvalid  := true.B
            axi_rresp   := 0.U
        }.elsewhen (axi_rvalid && RegIntf.RREADY) {
            axi_rvalid  := false.B
        }

        //
        // Read Enable Signal
        //
        slv_reg_rden    := axi_arready && RegIntf.ARVALID && ~axi_rvalid

        //
        // Output Select Logic
        //
        reg_data_out    := 0.U
        switch (axi_araddr(4, 2)) {
            is (0.U) { reg_data_out := Cat(reg_ie, 0.U(1.W)) }
            is (1.U) { reg_data_out := Cat(StatCode, IRQStatus.asUInt, Busy.asUInt) }
            is (3.U) { reg_data_out := reg_src_addr }
            is (4.U) { reg_data_out := reg_dst_addr }
            is (5.U) { reg_data_out := reg_length }
            is (6.U) { reg_data_out := MagicID.U }
        }

        //
        // axi_rdata
        //
        when (slv_reg_rden) {
            axi_rdata   := reg_data_out
        }

    } // withClockAndReset(ACLK, reset)
} // class RegFile

            for (n <- 0 to 3) {
                when (RegIntf.WSTRB(n) === 1.U) {
                    reg_src_addr_v(n)   := RegIntf.WDATA(n*8+7, n*8)
                }
            }


package aha
package dma

/* Chisel Imports */
import chisel3._

/**
 * Configuration of a Register File
 *
 * @constructor         constructs a configuration object for a register file
 * @param AddrWidth     the width of the address bus on the register file interface
 * @param DataWidth     the width of the data bus on the register file interface
 * @param MagicID       the value of the ID register in the registe file
 */

/**
 * RegFile Trait
 */
trait RegFile extends RawModule {

    // Clock and Reset
    val ACLK            = IO(Input(Clock()))
    val ARESETn         = IO(Input(Bool()))

    // Control Signals
    val GO_Pulse        = IO(Output(Bool()))
    val IRQEnable       = IO(Output(Bool()))
    val IRQClear_Pulse  = IO(Output(Bool()))
    val SrcAddr         = IO(Output(UInt(32.W)))
    val DstAddr         = IO(Output(UInt(32.W)))
    val Length          = IO(Output(UInt(32.W)))

    // Status Signals
    val Busy            = IO(Input(Bool()))
    val IRQStatus       = IO(Input(Bool()))
    val StatCode        = IO(Input(UInt(2.W)))

    // Register File Interface
    lazy val RegIntf    = IO(Flipped(getRegFileIntf))

    // Type  of Register File Interface
    type RegIntfTy <: Bundle

    // Register File Interface
    def getRegFileIntf : RegIntfTy

} // trait RegFile

/**
 * RegFile Factory
 */
object RegFile {

    /**
     * Build a register file based on the name and configuration provided
     */
    @throws(classOf[RuntimeException])
    def get(name: String, config: RegFileConfig) : RegFile  = {

        if ((config.AddrWidth != 32) || (config.DataWidth != 32)) {
            throw new RuntimeException(
                """register interfaces supports only address bus width
                of 32 and data bus width of 32""")
        } else {

            name.toLowerCase() match {

                // AXI Lite
                case "axilite" | "axi-lite" | "axi_lite" => new AXILiteRegFile(config.MagicID)

                // APB
                case "apb" => new APBRegFile(config.MagicID)

                // Unsupported
                case x => throw new RuntimeException(s"unsupported register file type $x")
            }

        }
    }
}

case class RegFileConfig(AddrWidth: Int, DataWidth: Int, MagicID: Int)


package aha
package dma

/* Chisel Imports */
import chisel3._
import chisel3.util.isPow2

/* Project Imports */
import aha.dma.CmdBundle
import aha.dma.util.{AXI4Intf, AXILiteIntf}

/**
 * DMA Top-Level Module
 *
 * @constructor         constructs a DMA engine with an AXI ID width, address bus
 *                      width, data bus width, store-and-forward fifo depth, and
 *                      the value of the peripheral ID register
 * @param IdWidth       the width of AXI ID signals (AWID, BID, ARID, and RID)
 * @param AddrWidth     the width of AXI address busses (AWADDR and ARADDR)
 * @param DataWidth     the width of AXI data busses (WDATA and RDATA)
 * @param FifoDepth     the depth of the store-and-forward fifo
 * @param RegFileName   the name of the register file type to instantiate
 * @param MagicID       the ID value to read from the ID_REG register
 *
 * @note transfer addresses must be aligned to the data bus width (DataWidth)
 * @note FifoDepth must be a power of 2
 */
class DMA ( IdWidth     : Int,
            AddrWidth   : Int,
            DataWidth   : Int,
            FifoDepth   : Int,
            RegFileName : String,
            MagicID     : Int = 0x5A5A5A5A ) extends RawModule {

    // FifoDepth must be a power of 2
    require(isPow2(FifoDepth))

    // =========================================================================
    // I/O
    // =========================================================================

    // Clock and Rese
    val ACLK            = IO(Input(Clock()))
    val ARESETn         = IO(Input(Bool()))

    // Interrupt
    val Irq             = IO(Output(Bool()))

    // AXI-Lite Register Interface
    lazy val RegIntf    = IO(Flipped(controller.reg_file.getRegFileIntf))

    // AXI4 Interface
    val M_AXI           = IO(new AXI4Intf(IdWidth, AddrWidth, DataWidth))

    // =========================================================================
    // Internal Logic
    // =========================================================================

    //
    // Instantiate Controller
    //
    val controller  = Module(   new Controller(
                                    AddrWidth,
                                    DataWidth,
                                    new CmdBundle(AddrWidth),
                                    new CmdBundle(AddrWidth),
                                    RegFileName,
                                    MagicID
                                )
                            )

    //
    // Instantiate DataMover
    //
    val data_mover  = Module(   new DataMover(
                                    IdWidth,
                                    AddrWidth,
                                    DataWidth,
                                    new CmdBundle(AddrWidth),
                                    new CmdBundle(AddrWidth),
                                    FifoDepth
                                )
                            )

    //

    data_mover.ARESETn      := ARESETn
    data_mover.M_AXI        <> M_AXI

    controller.RdCmdIntf    <> data_mover.RdCmdIntf
    controller.RdStatIntf   <> data_mover.RdStatIntf

    controller.WrCmdIntf    <> data_mover.WrCmdIntf
    controller.WrStatIntf   <> data_mover.WrStatIntf

    //
    // Change Top-Level Name of the Register File Interface Bundle
    //
    RegIntf.suggestName("RegIntf")

} // class DMA

    // Connections
    //

    controller.ACLK         := ACLK
    controller.ARESETn      := ARESETn


package aha
package dma

/* Scala Imports */
import scopt.OParser

/* Chisel Imports */
import chisel3._
import chisel3.stage.ChiselStage

/* Project Imports */
import aha.dma.util._

/**
 * DMA Configuration
 */
case class DMAConfig (
    // Width of AXI ID
    IdWidth: Int        = 2,

    // (Supported: axi-lite and apb)
    RegFileName: String = "apb",
    // Depth of Internal Store-and-Forward FIFO
    FifoDepth: Int      = 256,
    // Value of ID Register
    MagicID: Int        = 0x5A5A5A5A,
    // Artifact Output Folder
    OutputDir: String   = "output/"
)

object Main extends App {

    val builder = OParser.builder[DMAConfig]

    val parser = {
        import builder._
        OParser.sequence(
            programName("dma-builder"),
            head("aha-dma", "1.0"),

            opt[Int]('i', "id-width")
                .action((x, c) => c.copy(IdWidth = x))
                .text("width of AXI ID fields"),

            opt[Int]('a', "addr-width")
                .action((x, c) => c.copy(AddrWidth = x))
                .text("width of AXI address busses"),

            opt[Int]('d', "data-width")
                .action((x, c) => c.copy(DataWidth = x))
                .text("width of AXI data busses"),

            opt[String]('r', "reg-file")
                .action((x, c) => c.copy(RegFileName = x))
                .text("name of the type of register file to use (axi-lite or apb)"),

            opt[Int]('f', "fifo-depth")
                .action((x, c) => c.copy(FifoDepth = x))
                .text("depth of internal store-and-forward fifo (must be power of 2)"),

            opt[Int]('m', "magic-id")
                .action((x, c) => c.copy(MagicID = x))
                .text("value of ID register"),

            opt[String]('o', "output-dir")
                .action((x, c) => c.copy(OutputDir = x))
                .text("artifact output directory")
            )
        }

    OParser.parse(parser, args, DMAConfig()) match {
        case Some(config) => new ChiselStage().emitSystemVerilog(
                                new DMA(
                                    config.IdWidth,
                                    config.AddrWidth,
                                    config.DataWidth,
                                    config.FifoDepth,
                                    config.RegFileName,
                                    config.MagicID
                                ),
                                Array("--target-dir", config.OutputDir)
                             )
        case _ => ()
    }
} // object Main

    // Width of AXI Address Bus
    AddrWidth: Int      = 32,
    // Width of AXI Data Bus


package aha
package dma
package util

/* Chisel Imports */
import chisel3._

/**
 * AMBA 3 APB Protocol v1.0 Interface
 *
 * @constructor         constructs an APB interface bundle with the provided
 *                      address bus width and data bus width
 * @param AddrWidth     the width of the APB address bus (PADDR)
 * @param DataWidth     the width of the APB data busses (PWDATA and PRDATA)
 */
class APBIntf(AddrWidth: Int, DataWidth: Int) extends Bundle {
    val PADDR       = Output(UInt(AddrWidth.W))

    val PSEL        = Output(Bool())
    val PENABLE     = Output(Bool())
    val PWRITE      = Output(Bool())
    val PWDATA      = Output(UInt(DataWidth.W))
    val PREADY      = Input(Bool())


package aha
package dma
package util

/* Chisel Imports */
import chisel3._
import chisel3.util.Decoupled
import chisel3.experimental.dataview._

/**
 * AXI-Lite interface bundle
 *
 * @constructor         constructs an AXI-Lite interface bundle with the provided
 *                      address bus width and data bus width
 * @param AddrWidth     the width of AXI address busses (AWADDR and ARADDR)
 * @param DataWidth     the width of AXI data busses (WDATA and RDATA)
 */
class AXILiteIntf(AddrWidth: Int, DataWidth: Int) extends Bundle {

    // AW Channel
    val AWREADY     = Input(Bool())
    val AWVALID     = Output(Bool())
    val AWADDR      = Output(UInt(AddrWidth.W))
    val AWPROT      = Output(UInt(3.W))

    // W Channel
    val WREADY      = Input(Bool())
    val WVALID      = Output(Bool())
    val WDATA       = Output(UInt(DataWidth.W))
    val WSTRB       = Output(UInt((DataWidth/8).W))

    // B Channel
    val BREADY      = Output(Bool())
    val BVALID      = Input(Bool())
    val BRESP       = Input(UInt(2.W))

    // AR Channel
    val ARREADY     = Input(Bool())
    val ARVALID     = Output(Bool())
    val ARADDR      = Output(UInt(AddrWidth.W))
    val ARPROT      = Output(UInt(3.W))

    // R Channel
    val RREADY      = Output(Bool())
    val RVALID      = Input(Bool())
    val RDATA       = Input(UInt(DataWidth.W))
    val RRESP       = Input(UInt(2.W))
}

/**
 * Full AXI4 interface bundle
 *
 * @constructor         constructs an AXI4 interface bundle with the provided
 *                      AXI ID width, address bus width and data bus width
 * @param AddrWidth     the width of AXI address busses (AWADDR and ARADDR)
 * @param DataWidth     the width of AXI data busses (WDATA and RDATA)
 */
class AXI4Intf(val IdWidth: Int,
               val AddrWidth: Int,
               val DataWidth: Int) extends Bundle {

    //
    // Write Address Channel
    //
    val AWID        = Output(UInt(IdWidth.W))
    val AWADDR      = Output(UInt(AddrWidth.W))
    val AWLEN       = Output(UInt(8.W))
    val AWSIZE      = Output(UInt(3.W))
    val AWBURST     = Output(UInt(2.W))
    val AWLOCK      = Output(Bool())
    val AWCACHE     = Output(UInt(4.W))
    val AWPROT      = Output(UInt(3.W))
    val AWVALID     = Output(Bool())
    val AWREADY     = Input(Bool())

    //
    // Write Data Channel
    //
    val WDATA       = Output(UInt(DataWidth.W))
    val WSTRB       = Output(UInt((DataWidth/8).W))
    val WLAST       = Output(Bool())
    val WVALID      = Output(Bool())
    val WREADY      = Input(Bool())

    //
    // Write Response Channel
    //
    val BID         = Input(UInt(IdWidth.W))
    val BRESP       = Input(UInt(2.W))
    val BVALID      = Input(Bool())
    val BREADY      = Output(Bool())

    //
    // Read Address Channel
    //
    val ARID        = Output(UInt(IdWidth.W))
    val ARADDR      = Output(UInt(AddrWidth.W))
    val ARLEN       = Output(UInt(8.W))
    val ARSIZE      = Output(UInt(3.W))
    val ARBURST     = Output(UInt(2.W))
    val ARLOCK      = Output(Bool())
    val ARCACHE     = Output(UInt(4.W))
    val ARPROT      = Output(UInt(3.W))
    val ARVALID     = Output(Bool())
    val ARREADY     = Input(Bool())

    //
    // Read Data Channel
    //
    val RID         = Input(UInt(IdWidth.W))
    val RDATA       = Input(UInt(DataWidth.W))
    val RRESP       = Input(UInt(2.W))
    val RLAST       = Input(Bool())
    val RVALID      = Input(Bool())
    val RREADY      = Output(Bool())
}

/**
 * Bundle providing the write view of an AXI4 interface
 *
 * @constructor         constructs an AXI4 write-view interface bundle with the
 *                      provided AXI ID width, address bus width and data bus width
 * @param IdWidth       the width of AXI ID signals (AWID, BID, ARID, and RID)
 * @param AddrWidth     the width of AXI address busses (AWADDR and ARADDR)
 * @param DataWidth     the width of AXI data busses (WDATA and RDATA)
 */
class AXI4WrIntfView(val IdWidth: Int,
                     val AddrWidth: Int,
                     val DataWidth: Int) extends Bundle {

    val AW      = Decoupled(new AXI4AddrPayload(IdWidth, AddrWidth))
    val W       = Decoupled(new AXI4WrDataPayload(DataWidth))
    val B       = Flipped(Decoupled(new AXI4WrRespPayload(IdWidth)))
}

/**
 * Provide the Chisel DataView implicit converters to allow viewing an AXI4 full
 * interface in write-view
 * {{
 *      val fullAxi   = new AXI4Intf(IdWIdth, AddrWidth, DataWidth)
 *      val writeView = fullAxi.views[AXI4WrIntfView]
 * }}
 */
object AXI4WrIntfView {
    implicit val wrIntfView = PartialDataView[AXI4Intf, AXI4WrIntfView] (

        // Interface Mapping
        axi4 => new AXI4WrIntfView(axi4.IdWidth, axi4.AddrWidth, axi4.DataWidth),

        // Signal Mapping for AW Channel
        _.AWID      -> _.AW.bits.ID,
        _.AWADDR    -> _.AW.bits.ADDR,
        _.AWLEN     -> _.AW.bits.LEN,
        _.AWSIZE    -> _.AW.bits.SIZE,
        _.AWBURST   -> _.AW.bits.BURST,
        _.AWLOCK    -> _.AW.bits.LOCK,
        _.AWCACHE   -> _.AW.bits.CACHE,
        _.AWPROT    -> _.AW.bits.PROT,
        _.AWVALID   -> _.AW.valid,
        _.AWREADY   -> _.AW.ready,

        // Signal Mapping for W Channel
        _.WDATA     -> _.W.bits.DATA,
        _.WSTRB     -> _.W.bits.STRB,
        _.WLAST     -> _.W.bits.LAST,
        _.WVALID    -> _.W.valid,
        _.WREADY    -> _.W.ready,

        // Signal Mapping for B Channel
        _.BID       -> _.B.bits.ID,
        _.BRESP     -> _.B.bits.RESP,
        _.BVALID    -> _.B.valid,
        _.BREADY    -> _.B.ready
    )
}

/**
 * Bundle providing the read view of an AXI4 interface
 *
 * @constructor         constructs an AXI4 read-view interface bundle with the
 *                      provided AXI ID width, address bus width and data bus width
 * @param IdWidth       the width of AXI ID signals (AWID, BID, ARID, and RID)
 * @param AddrWidth     the width of AXI address busses (AWADDR and ARADDR)
 * @param DataWidth     the width of AXI data busses (WDATA and RDATA)
 */
class AXI4RdIntfView(val IdWidth: Int,
                     val AddrWidth: Int,
                     val DataWidth: Int) extends Bundle {

    val AR  = Decoupled(new AXI4AddrPayload(IdWidth, AddrWidth))
    val R   = Flipped(Decoupled(new AXI4RdDataPayload(IdWidth, DataWidth)))
}

/**
 * Provide the Chisel DataView implicit converters to allow viewing an AXI4 full
 * interface in read-view
 * {{
 *      val fullAxi   = new AXI4Intf(IdWIdth, AddrWidth, DataWidth)
 *      val readView = fullAxi.views[AXI4RdIntfView]
 * }}
 */
object AXI4RdIntfView {
    implicit val rdIntfView = PartialDataView[AXI4Intf, AXI4RdIntfView] (

        // Interface Mapping
        axi4 => new AXI4RdIntfView(axi4.IdWidth, axi4.AddrWidth, axi4.DataWidth),

        // Signal Mapping for AR Channel
        _.ARID      -> _.AR.bits.ID,
        _.ARADDR    -> _.AR.bits.ADDR,
        _.ARLEN     -> _.AR.bits.LEN,
        _.ARSIZE    -> _.AR.bits.SIZE,
        _.ARBURST   -> _.AR.bits.BURST,
        _.ARLOCK    -> _.AR.bits.LOCK,
        _.ARCACHE   -> _.AR.bits.CACHE,
        _.ARPROT    -> _.AR.bits.PROT,
        _.ARVALID   -> _.AR.valid,
        _.ARREADY   -> _.AR.ready,

        // Signal Mapping for R Channel
        _.RID       -> _.R.bits.ID,
        _.RDATA     -> _.R.bits.DATA,
        _.RRESP     -> _.R.bits.RESP,
        _.RLAST     -> _.R.bits.LAST,
        _.RVALID    -> _.R.valid,
        _.RREADY    -> _.R.ready
    )
}

/**
 * AXI4 address bus bundle
 *
 * @constructor         constructs axi AXI4 address bundle with the provided
 *                      AXI ID width and address bus width
 * @param IdWidth       the width of AXI ID signals (AWID, BID, ARID, and RID)
 * @param AddrWidth     the width of AXI address busses (AWADDR and ARADDR)
 */
class AXI4AddrPayload(IdWidth: Int, AddrWidth: Int) extends Bundle {
    val ID      = UInt(IdWidth.W)
    val ADDR    = UInt(AddrWidth.W)
    val LEN     = UInt(8.W)
    val SIZE    = UInt(3.W)
    val BURST   = UInt(2.W)
    val LOCK    = Bool()
    val CACHE   = UInt(4.W)
    val PROT    = UInt(3.W)
}

/**
 * AXI4 write data bus bundle
 *
 * @constructor         constructs axi AXI4 write data bundle with the provided
 *                      AXI data bus width
 * @param DataWidth     the width of AXI data busses (WDATA and RDATA)
 */
class AXI4WrDataPayload(DataWidth: Int) extends Bundle {
    val DATA    = UInt(DataWidth.W)
    val STRB    = UInt((DataWidth/8).W)
    val LAST    = Bool()
}

/**
 * AXI4 write response bus bundle
 *
 * @constructor         constructs axi AXI4 write response bundle with the provided
 *                      AXI ID width
 * @param IdWidth       the width of AXI ID signals (AWID, BID, ARID, and RID)
 */
class AXI4WrRespPayload(IdWidth: Int) extends Bundle {
    val ID      = UInt(IdWidth.W)
    val RESP    = UInt(2.W)
}

/**
 * AXI4 read data bus bundle
 *
 * @constructor         constructs axi AXI4 read data bundle with the provided
 *                      AXI ID width and data bus width
 * @param IdWidth       the width of AXI ID signals (AWID, BID, ARID, and RID)
 * @param DataWidth     the width of AXI data busses (WDATA and RDATA)
 */
class AXI4RdDataPayload(IdWidth: Int, DataWidth: Int) extends Bundle {
    val ID      = UInt(IdWidth.W)
    val DATA    = UInt(DataWidth.W)

    val RESP    = UInt(2.W)
    val LAST    = Bool()


package aha
package dma
package util

/* Chisel Imports */
import chisel3._
import chisel3.util.{Decoupled, isPow2}

/* Project Imports */
import aha.dma.clog2

/**
 * Peek queue (always puts the current value pointed to by the read pointer on
 * the dequeue interface)
 *
 * @constructor         constructs a PeekQueue with the provided element type and
 *                      depth
 * @tparam T            the type of elements to store in the queue
 * @param Depth         the depth of the queue (must be a power of 2)
 */
class PeekQueue[T <: Data](Elem: T, Depth: Int) extends RawModule {

    // Depth must be a power of 2
    require(isPow2(Depth))

    // =========================================================================
    // I/O
    // =========================================================================

    // Clock and Reset
    val ACLK        = IO(Input(Clock()))
    val ARESETn     = IO(Input(Bool()))

    // Enqueue Interface
    val EnqIntf     = IO(Flipped(Decoupled(Elem)))

    // Dequeue Interface
    val DeqIntf     = IO(Decoupled(Elem))

    // =========================================================================
    // Chisel Work-Around for Active-Low Reset
    // =========================================================================

    val reset       = (!ARESETn).asAsyncReset

    // =========================================================================
    // Internal Logic
    // =========================================================================

    val ADDR_WIDTH  = clog2(Depth);

    val counter     = withClockAndReset(ACLK, reset) { RegInit(0.U((ADDR_WIDTH + 1).W)) }

    val wrptr       = withClockAndReset(ACLK, reset) { RegInit(0.U(ADDR_WIDTH.W)) }

    // Internal Memory
    val mem         = Mem(Depth, Elem)

    withClockAndReset(ACLK, reset) {

        //
        // Counter Update Logic
        //
        when (EnqIntf.ready && EnqIntf.valid && DeqIntf.ready && DeqIntf.valid) {
            // Simultaneous Enqueue and Dequeue
            counter     := counter
        }.elsewhen (EnqIntf.ready && EnqIntf.valid) {
            // Enqueue
            counter     := counter + 1.U
        }.elsewhen (DeqIntf.ready && DeqIntf.valid) {
            // Dequeue
            counter     := counter - 1.U
        }

        //
        // Read Pointer Update Logic
        //
        when (DeqIntf.ready && DeqIntf.valid) {
            rdptr       := rdptr + 1.U
        }

        //
        // Write Pointer Update Logic
        //
        when (EnqIntf.ready && EnqIntf.valid) {
            wrptr       := wrptr + 1.U
        }

        //
        // Enqueue Operation (Synchronous)
        //
        when (EnqIntf.ready && EnqIntf.valid) {
            mem(wrptr)  := EnqIntf.bits
        }

        //
        // Read Operation (Asynchronous)
        //
        DeqIntf.bits    := mem(rdptr)

        //
        // Enqueue Ready and Dequeue Valid
        //
        EnqIntf.ready   := counter =/= Depth.U
        DeqIntf.valid   := counter =/= 0.U

    } // withClockAndReset(ACLK, reset_n)
}

val rdptr       = withClockAndReset(ACLK, reset) { RegInit(0.U(ADDR_WIDTH.W)) }


package aha
package dma

/* Chisel Imports */
import chisel3._
import chisel3.experimental.ChiselEnum
import chisel3.util.{Decoupled, Fill, is, switch}

/* Project Imports */
import aha.dma.{clog2, CmdBundle, log2, MAX_BURST_BYTES}
import aha.dma.util.{AXI4WrIntfView, AXI4AddrPayload}

/**
 * AXI4 Write Engine
 *
 * @constructor         constructs a AXI4 write engine with an AXI ID width,
 *                      address bus width, data bus width, and the type of a
 *                      transfer command bundle
 * @tparam W            transfer command bundle type for writer
 * @param IdWidth       the width of AXI ID signals (AWID, BID, ARID, and RID)
 * @param AddrWidth     the width of AXI address busses (AWADDR and ARADDR)
 * @param DataWidth     the width of AXI data busses (WDATA and RDATA)
 * @param Cmd           the bundle to use for transfer commands
 *
 * @note transfer addresses must be aligned to the data bus width (DataWidth)
 * @note transfers must not cross the [[MAX_BURST_BYTES]]
 */
class Writer[W <: CmdBundle](IdWidth: Int,
                             AddrWidth: Int,
                             DataWidth: Int,
                             Cmd : W) extends RawModule {

     import Writer.State
     import Writer.State._

     // =========================================================================
     // I/O
     // =========================================================================

     // Clock and Reset
     val ACLK       = IO(Input(Clock()))
     val ARESETn    = IO(Input(Bool()))

     // Command/Stat Interfaces
     val CmdIntf    = IO(Flipped(Decoupled(Cmd)))
     val StatIntf   = IO(Decoupled(UInt(2.W)))

     // Data Packet Interface
     val DataIntf   = IO(Flipped(Decoupled(UInt(DataWidth.W))))

     // AXI4 Write Interface
     val WriteIntf  = IO(new AXI4WrIntfView(IdWidth, AddrWidth, DataWidth))

     // =========================================================================
     // Chisel Work-Around for Active-Low Reset
     // =========================================================================

     val reset      = (!ARESETn).asAsyncReset

     // =========================================================================
     // Internal Logic
     // =========================================================================

     val n_lanes    = DataWidth/8

     val state      = withClockAndReset(ACLK, reset) { RegInit(sIDLE) }
     val write_resp = withClockAndReset(ACLK, reset) { RegInit(0.U(2.W)) }
     val num_bytes  = withClockAndReset(ACLK, reset) { RegInit(0.U(clog2(MAX_BURST_BYTES).W)) }
     val last_beat  = Wire(Bool())

     // AW Channel Signals
     val aw_size    = log2(DataWidth/8)
     val aw_addr    = (CmdIntf.bits.Address >> aw_size.U) << aw_size.U
     val aw_len     = Wire(UInt(8.W))

     val aw_payload = withClockAndReset(ACLK, reset) {
                         RegInit(0.U.asTypeOf(new AXI4AddrPayload(IdWidth, AddrWidth)))
                       }

     // W Channel Signals
     val w_strb     = Wire(UInt((DataWidth/8).W))
     val w_valid    = Wire(Bool())
     val w_last     = Wire(Bool())

     withClockAndReset(ACLK, reset) {

         //
         // State Register Update Logic
         //
         switch (state) {
             is (sIDLE) {
                 when (CmdIntf.valid) {
                     state  := sADDR
                 }
             } // sIDLE
             is (sADDR) {
                when (WriteIntf.AW.ready) {
                    state   := sDATA
                }
             } // sADDR
             is (sDATA) {
                 when (last_beat) {
                     state  := sRESP
                 }
             } // sDATA
             is (sRESP) {
                 when (WriteIntf.B.valid) {
                     state  := sSTAT
                 }
             } // sRESP
             is (sSTAT) {
                 when (StatIntf.ready) {
                     state  := sIDLE
                 }
             }
         } // switch(state)

         //
         // AW Channel Signals
         //
         switch (state) {
             is (sIDLE) {
                 when (CmdIntf.valid) {
                     aw_valid            := true.B
                     aw_payload.ID       := 0.U
                     aw_payload.ADDR     := aw_addr
                     aw_payload.LEN      := aw_len
                     aw_payload.SIZE     := aw_size.U
                     aw_payload.BURST    := 1.U
                     aw_payload.LOCK     := false.B
                     aw_payload.CACHE    := 2.U
                     aw_payload.PROT     := 2.U
                 }.otherwise {
                     aw_valid            := false.B
                 }
             } // sIDLE
             is (sADDR) {
                 when (WriteIntf.AW.ready) {
                     aw_valid            := false.B
                 }
             } // sADDR
         } // switch(state)

         WriteIntf.AW.valid     := aw_valid
         WriteIntf.AW.bits      := aw_payload

         //
         // Write Response Capture
         //
         switch (state) {
             is (sIDLE) {
                 write_resp     := 0.U
             } // sIDLE
             is (sRESP) {
                 when (WriteIntf.B.valid) {
                     write_resp := WriteIntf.B.bits.RESP
                 }
             } // sRESP
         } // switch (state)

         //
         // WDATA
         //
         when (state === sDATA) {
             WriteIntf.W.bits.DATA  := DataIntf.bits
         }.otherwise {
             WriteIntf.W.bits.DATA  := 0.U
         }

         //
         // num_bytes
         //
         switch (state) {
             is (sIDLE) {
                 when (CmdIntf.valid) {
                     num_bytes  := CmdIntf.bits.NumBytes
                 }.otherwise {
                     num_bytes  := 0.U
                 }
             } // sIDLE
             is (sDATA) {
                when (WriteIntf.W.valid && WriteIntf.W.ready) {
                    when (num_bytes > n_lanes.U) {
                        num_bytes   := num_bytes - n_lanes.U
                    }
                }
             } // sDATA
             is (sRESP) {
                 num_bytes  := 0.U
             } // sRESP
         } // switch (state)

         //
         // WSTRB
         //
         val choices = VecInit((0 until n_lanes).map(x => (1 << x) - 1).map(_.U))
         when (state === sDATA) {
             when (num_bytes >= n_lanes.U) {
                 w_strb  := Fill(n_lanes, 1.U(1.W))
             }.otherwise {
                 w_strb  := choices(num_bytes(log2(n_lanes)-1, 0))
             }
         }.otherwise {
             w_strb  := 0.U
         }

         WriteIntf.W.bits.STRB  := w_strb

         //
         // WVALID
         //
         when (state === sDATA) {
             w_valid    := DataIntf.valid
         }.otherwise {
             w_valid    := false.B
         }

         WriteIntf.W.valid  := w_valid

         //
         // WLAST
         //
         when (state === sDATA) {
             w_last     := num_bytes <= n_lanes.U
         }.otherwise {
             w_last     := false.B
         }

         WriteIntf.W.bits.LAST  := w_last

         //
         // Internal Signals
         //
         last_beat  := WriteIntf.W.valid && WriteIntf.W.ready && WriteIntf.W.bits.LAST
         aw_len      := ((CmdIntf.bits.NumBytes >> aw_size.U) +
                          CmdIntf.bits.NumBytes(aw_size-1, 0).orR) - 1.U

         //
         // BREADY
         //
         WriteIntf.B.ready  := state === sRESP

         //
         // Status Signals
         //
         StatIntf.valid     := state === sSTAT
         StatIntf.bits      := write_resp

         //
         // Command Interface Ready
         //
         CmdIntf.ready  := state === sIDLE

         //
         // Data Interface Ready
         //
         DataIntf.ready := (state === sDATA) && WriteIntf.W.ready

     } // withClockAndReset(ACLK, reset)
}

/**
 * Provides the states for the [[Writer]] finite state machine
 */
object Writer {

    /**
     * States for Writer FSM
     */
    object State extends ChiselEnum {
        val sIDLE, sADDR, sDATA, sRESP, sSTAT = Value
    } // object State
} // object Writer

val aw_valid   = withClockAndReset(ACLK, reset) { RegInit(false.B) }


package icenet

import chisel3._
import chisel3.util._
import freechips.rocketchip.unittest.UnitTest
import IceNetConsts._

class Aligner(dataBits: Int) extends Module {
  val dataBytes = dataBits / 8

  val io = IO(new StreamIO(dataBits))

  val data = RegInit(0.U(dataBits.W))
  val keep = RegInit(0.U(dataBytes.W))
  val last = RegInit(false.B)
  val nbytes = RegInit(0.U(log2Ceil(dataBytes + 1).W))

  assert(!io.in.valid || io.in.bits.keep.orR,
    "Aligner cannot handle an empty flit")

  val rshift = PriorityEncoder(io.in.bits.keep)
  val full_keep = ((io.in.bits.keep >> rshift) << nbytes) | keep

  val in_mask = FillInterleaved(8, io.in.bits.keep)
  val in_data = io.in.bits.data & in_mask

  val rshift_bit = Cat(rshift, 0.U(3.W))
  val nbits = Cat(nbytes, 0.U(3.W))
  val bitmask = FillInterleaved(8, keep)
  val full_data = ((in_data >> rshift_bit) << nbits) | (data & bitmask)
  val full_nbytes = PopCount(full_keep)
  val fwd_last = io.in.bits.last && (full_keep >> dataBytes.U) === 0.U

  io.out.valid := (last && nbytes > 0.U) ||
                  (io.in.valid && (fwd_last || full_nbytes >= dataBytes.U))
  io.out.bits.data := Mux(last, data, full_data(dataBits-1, 0))
  io.out.bits.keep := Mux(last, keep, full_keep(dataBytes-1, 0))
  io.out.bits.last := last || fwd_last

  io.in.ready := full_nbytes < dataBytes.U ||
                 (io.out.ready && !last)

  when (io.in.fire && io.out.fire) {
    data := full_data >> dataBits.U
    keep := full_keep >> dataBytes.U
    last := io.in.bits.last && !fwd_last
    nbytes := Mux(fwd_last, 0.U, full_nbytes - dataBytes.U)
  } .elsewhen (io.in.fire) {
    data := full_data
    keep := full_keep
    last := io.in.bits.last
    nbytes := full_nbytes
  } .elsewhen (io.out.fire) {
    data := 0.U
    keep := 0.U
    last := false.B
    nbytes := 0.U
  }
}

class AlignerTest extends UnitTest {
  val inData = VecInit(
    "h0011223344556677".U,
    "h8899AABBCCDDEEFF".U,
    "h0123456789ABCDEF".U,
    "hFEDCBA9876543210".U)
  val inKeep = VecInit(
    "b11111100".U,
    "b01111000".U,
    "b00001111".U,
    "b11110000".U)
  val inLast = VecInit(false.B, false.B, true.B, true.B)

  val outData = VecInit(
    "hBBCC001122334455".U,
    "h000089ABCDEF99AA".U,
    "h00000000FEDCBA98".U)
  val outKeep = VecInit(
    "b11111111".U,
    "b00111111".U,
    "b00001111".U)
  val outLast = VecInit(false.B, true.B, true.B)

  val started = RegInit(false.B)
  val sending = RegInit(false.B)
  val receiving = RegInit(false.B)

  val aligner = Module(new Aligner(NET_IF_WIDTH))

  val (inIdx, inDone) = Counter(aligner.io.in.fire, inData.size)
  val (outIdx, outDone) = Counter(aligner.io.out.fire, outData.size)

  aligner.io.in.valid := sending
  aligner.io.in.bits.data := inData(inIdx)
  aligner.io.in.bits.keep := inKeep(inIdx)
  aligner.io.in.bits.last := inLast(inIdx)
  aligner.io.out.ready := receiving

  when (io.start && !started) {
    started := true.B
    sending := true.B
    receiving := true.B
  }

  when (outDone) { receiving := false.B }

  io.finished := started && !sending && !receiving

  def compareData(a: UInt, b: UInt, keep: UInt) = {
    val bitmask = FillInterleaved(8, keep)
    (a & bitmask) === (b & bitmask)
  }

  assert(!aligner.io.out.valid ||
    (compareData(
      aligner.io.out.bits.data,
      outData(outIdx),
      aligner.io.out.bits.keep) &&
     aligner.io.out.bits.keep === outKeep(outIdx) &&
     aligner.io.out.bits.last === outLast(outIdx)),
   "AlignerTest: output does not match expected")
}