Merge remote-tracking branch 'junctions/master' into mono-repo
This commit is contained in:
commit
a5b88d0bdc
17
junctions/.gitignore
vendored
Normal file
17
junctions/.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
*.class
|
||||
*.log
|
||||
|
||||
# sbt specific
|
||||
.cache
|
||||
.history
|
||||
.lib/
|
||||
dist/*
|
||||
target/
|
||||
lib_managed/
|
||||
src_managed/
|
||||
project/boot/
|
||||
project/plugins/project/
|
||||
|
||||
# Scala-IDE specific
|
||||
.scala_dependencies
|
||||
.worksheet
|
28
junctions/LICENSE
Normal file
28
junctions/LICENSE
Normal file
@ -0,0 +1,28 @@
|
||||
Copyright (c) 2015, The Regents of the University of California (Regents)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of junctions nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
|
||||
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING
|
||||
OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS
|
||||
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED
|
||||
HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE
|
||||
MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
|
6
junctions/README.md
Normal file
6
junctions/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# junctions
|
||||
A repository for peripheral components and IO devices associated with the RocketChip project.
|
||||
|
||||
To uses these modules, include this repo as a git submodule within the your chip repository and add it as Project in your chip's build.scala. These components are only dependent on Chisel, i.e.
|
||||
|
||||
lazy val junctions = project.dependsOn(chisel)
|
19
junctions/build.sbt
Normal file
19
junctions/build.sbt
Normal file
@ -0,0 +1,19 @@
|
||||
organization := "edu.berkeley.cs"
|
||||
|
||||
version := "1.0"
|
||||
|
||||
name := "junctions"
|
||||
|
||||
scalaVersion := "2.11.6"
|
||||
|
||||
// Provide a managed dependency on chisel if -DchiselVersion="" is supplied on the command line.
|
||||
libraryDependencies ++= (Seq("chisel","cde").map {
|
||||
dep: String => sys.props.get(dep + "Version") map { "edu.berkeley.cs" %% dep % _ }}).flatten
|
||||
|
||||
site.settings
|
||||
|
||||
site.includeScaladoc()
|
||||
|
||||
ghpages.settings
|
||||
|
||||
git.remoteRepo := "git@github.com:ucb-bar/junctions.git"
|
5
junctions/project/plugins.sbt
Normal file
5
junctions/project/plugins.sbt
Normal file
@ -0,0 +1,5 @@
|
||||
resolvers += "jgit-repo" at "http://download.eclipse.org/jgit/maven"
|
||||
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.3")
|
||||
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.1")
|
150
junctions/src/main/scala/addrmap.scala
Normal file
150
junctions/src/main/scala/addrmap.scala
Normal file
@ -0,0 +1,150 @@
|
||||
// See LICENSE for license details.
|
||||
|
||||
package junctions
|
||||
|
||||
import Chisel._
|
||||
import cde.{Parameters, Field}
|
||||
import scala.collection.mutable.HashMap
|
||||
|
||||
case object PAddrBits extends Field[Int]
|
||||
case object VAddrBits extends Field[Int]
|
||||
case object PgIdxBits extends Field[Int]
|
||||
case object PgLevels extends Field[Int]
|
||||
case object PgLevelBits extends Field[Int]
|
||||
case object ASIdBits extends Field[Int]
|
||||
case object PPNBits extends Field[Int]
|
||||
case object VPNBits extends Field[Int]
|
||||
|
||||
case object GlobalAddrMap extends Field[AddrMap]
|
||||
|
||||
trait HasAddrMapParameters {
|
||||
implicit val p: Parameters
|
||||
|
||||
val paddrBits = p(PAddrBits)
|
||||
val vaddrBits = p(VAddrBits)
|
||||
val pgIdxBits = p(PgIdxBits)
|
||||
val ppnBits = p(PPNBits)
|
||||
val vpnBits = p(VPNBits)
|
||||
val pgLevels = p(PgLevels)
|
||||
val pgLevelBits = p(PgLevelBits)
|
||||
val asIdBits = p(ASIdBits)
|
||||
|
||||
val addrMap = p(GlobalAddrMap)
|
||||
}
|
||||
|
||||
case class MemAttr(prot: Int, cacheable: Boolean = false)
|
||||
|
||||
sealed abstract class MemRegion {
|
||||
def start: BigInt
|
||||
def size: BigInt
|
||||
def numSlaves: Int
|
||||
def attr: MemAttr
|
||||
|
||||
def containsAddress(x: UInt) = UInt(start) <= x && x < UInt(start + size)
|
||||
}
|
||||
|
||||
case class MemSize(size: BigInt, attr: MemAttr) extends MemRegion {
|
||||
def start = 0
|
||||
def numSlaves = 1
|
||||
}
|
||||
|
||||
case class MemRange(start: BigInt, size: BigInt, attr: MemAttr) extends MemRegion {
|
||||
def numSlaves = 1
|
||||
}
|
||||
|
||||
object AddrMapProt {
|
||||
val R = 0x1
|
||||
val W = 0x2
|
||||
val X = 0x4
|
||||
val RW = R | W
|
||||
val RX = R | X
|
||||
val RWX = R | W | X
|
||||
val SZ = 3
|
||||
}
|
||||
|
||||
class AddrMapProt extends Bundle {
|
||||
val x = Bool()
|
||||
val w = Bool()
|
||||
val r = Bool()
|
||||
}
|
||||
|
||||
case class AddrMapEntry(name: String, region: MemRegion)
|
||||
|
||||
object AddrMap {
|
||||
def apply(elems: AddrMapEntry*): AddrMap = new AddrMap(elems)
|
||||
}
|
||||
|
||||
class AddrMap(entriesIn: Seq[AddrMapEntry], val start: BigInt = BigInt(0)) extends MemRegion {
|
||||
def isEmpty = entries.isEmpty
|
||||
def length = entries.size
|
||||
def numSlaves = entries.map(_.region.numSlaves).foldLeft(0)(_ + _)
|
||||
def attr = ???
|
||||
|
||||
private val slavePorts = HashMap[String, Int]()
|
||||
private val mapping = HashMap[String, MemRegion]()
|
||||
|
||||
val (size: BigInt, entries: Seq[AddrMapEntry]) = {
|
||||
var ind = 0
|
||||
var base = start
|
||||
var rebasedEntries = collection.mutable.ArrayBuffer[AddrMapEntry]()
|
||||
for (AddrMapEntry(name, r) <- entriesIn) {
|
||||
if (r.start != 0) {
|
||||
val align = BigInt(1) << log2Ceil(r.size)
|
||||
require(r.start >= base, s"region $name base address 0x${r.start.toString(16)} overlaps previous base 0x${base.toString(16)}")
|
||||
require(r.start % align == 0, s"region $name base address 0x${r.start.toString(16)} not aligned to 0x${align.toString(16)}")
|
||||
base = r.start
|
||||
} else {
|
||||
base = (base + r.size - 1) / r.size * r.size
|
||||
}
|
||||
|
||||
r match {
|
||||
case r: AddrMap =>
|
||||
val subMap = new AddrMap(r.entries, base)
|
||||
rebasedEntries += AddrMapEntry(name, subMap)
|
||||
mapping += name -> subMap
|
||||
mapping ++= subMap.mapping.map { case (k, v) => s"$name:$k" -> v }
|
||||
slavePorts ++= subMap.slavePorts.map { case (k, v) => s"$name:$k" -> (ind + v) }
|
||||
case _ =>
|
||||
val e = MemRange(base, r.size, r.attr)
|
||||
rebasedEntries += AddrMapEntry(name, e)
|
||||
mapping += name -> e
|
||||
slavePorts += name -> ind
|
||||
}
|
||||
|
||||
ind += r.numSlaves
|
||||
base += r.size
|
||||
}
|
||||
(base - start, rebasedEntries)
|
||||
}
|
||||
|
||||
val flatten: Seq[(String, MemRange)] = {
|
||||
val arr = new Array[(String, MemRange)](slavePorts.size)
|
||||
for ((name, port) <- slavePorts)
|
||||
arr(port) = (name, mapping(name).asInstanceOf[MemRange])
|
||||
arr
|
||||
}
|
||||
|
||||
def apply(name: String): MemRegion = mapping(name)
|
||||
def port(name: String): Int = slavePorts(name)
|
||||
def subMap(name: String): AddrMap = mapping(name).asInstanceOf[AddrMap]
|
||||
def isInRegion(name: String, addr: UInt): Bool = mapping(name).containsAddress(addr)
|
||||
|
||||
def isCacheable(addr: UInt): Bool = {
|
||||
flatten.filter(_._2.attr.cacheable).map { case (_, region) =>
|
||||
region.containsAddress(addr)
|
||||
}.foldLeft(Bool(false))(_ || _)
|
||||
}
|
||||
|
||||
def isValid(addr: UInt): Bool = {
|
||||
flatten.map { case (_, region) =>
|
||||
region.containsAddress(addr)
|
||||
}.foldLeft(Bool(false))(_ || _)
|
||||
}
|
||||
|
||||
def getProt(addr: UInt): AddrMapProt = {
|
||||
val protForRegion = flatten.map { case (_, region) =>
|
||||
Mux(region.containsAddress(addr), UInt(region.attr.prot, AddrMapProt.SZ), UInt(0))
|
||||
}
|
||||
new AddrMapProt().fromBits(protForRegion.reduce(_|_))
|
||||
}
|
||||
}
|
333
junctions/src/main/scala/atos.scala
Normal file
333
junctions/src/main/scala/atos.scala
Normal file
@ -0,0 +1,333 @@
|
||||
package junctions
|
||||
|
||||
import Chisel._
|
||||
import scala.math.max
|
||||
import cde.{Parameters, Field}
|
||||
|
||||
trait HasAtosParameters extends HasNastiParameters {
|
||||
// round up to a multiple of 32
|
||||
def roundup(n: Int) = 32 * ((n - 1) / 32 + 1)
|
||||
|
||||
val atosUnionBits = max(
|
||||
nastiXIdBits + nastiXDataBits + nastiWStrobeBits + 1,
|
||||
nastiXIdBits + nastiXBurstBits +
|
||||
nastiXSizeBits + nastiXLenBits + nastiXAddrBits)
|
||||
val atosIdBits = nastiXIdBits
|
||||
val atosTypBits = 2
|
||||
val atosRespBits = nastiXRespBits
|
||||
val atosDataBits = nastiXDataBits
|
||||
|
||||
val atosAddrOffset = atosIdBits
|
||||
val atosLenOffset = atosIdBits + nastiXAddrBits
|
||||
val atosSizeOffset = atosLenOffset + nastiXLenBits
|
||||
val atosBurstOffset = atosSizeOffset + nastiXSizeBits
|
||||
|
||||
val atosDataOffset = atosIdBits
|
||||
val atosStrobeOffset = nastiXDataBits + atosIdBits
|
||||
val atosLastOffset = atosStrobeOffset + nastiWStrobeBits
|
||||
|
||||
val atosRequestBits = roundup(atosTypBits + atosUnionBits)
|
||||
val atosResponseBits = roundup(atosTypBits + atosIdBits + atosRespBits + atosDataBits + 1)
|
||||
val atosRequestBytes = atosRequestBits / 8
|
||||
val atosResponseBytes = atosResponseBits / 8
|
||||
val atosRequestWords = atosRequestBytes / 4
|
||||
val atosResponseWords = atosResponseBytes / 4
|
||||
}
|
||||
|
||||
abstract class AtosModule(implicit val p: Parameters)
|
||||
extends Module with HasAtosParameters
|
||||
abstract class AtosBundle(implicit val p: Parameters)
|
||||
extends ParameterizedBundle()(p) with HasAtosParameters
|
||||
|
||||
object AtosRequest {
|
||||
def arType = UInt("b00")
|
||||
def awType = UInt("b01")
|
||||
def wType = UInt("b10")
|
||||
|
||||
def apply(typ: UInt, union: UInt)(implicit p: Parameters): AtosRequest = {
|
||||
val areq = Wire(new AtosRequest)
|
||||
areq.typ := typ
|
||||
areq.union := union
|
||||
areq
|
||||
}
|
||||
|
||||
def apply(ar: NastiReadAddressChannel)(implicit p: Parameters): AtosRequest =
|
||||
apply(arType, Cat(ar.burst, ar.size, ar.len, ar.addr, ar.id))
|
||||
|
||||
def apply(aw: NastiWriteAddressChannel)(implicit p: Parameters): AtosRequest =
|
||||
apply(awType, Cat(aw.burst, aw.size, aw.len, aw.addr, aw.id))
|
||||
|
||||
def apply(w: NastiWriteDataChannel)(implicit p: Parameters): AtosRequest =
|
||||
apply(wType, Cat(w.last, w.strb, w.data, w.id))
|
||||
}
|
||||
|
||||
class AtosRequest(implicit p: Parameters)
|
||||
extends AtosBundle()(p) with Serializable {
|
||||
val typ = UInt(width = atosTypBits)
|
||||
val union = UInt(width = atosUnionBits)
|
||||
|
||||
def burst(dummy: Int = 0) =
|
||||
union(atosUnionBits - 1, atosBurstOffset)
|
||||
|
||||
def size(dummy: Int = 0) =
|
||||
union(atosBurstOffset - 1, atosSizeOffset)
|
||||
|
||||
def len(dummy: Int = 0) =
|
||||
union(atosSizeOffset - 1, atosLenOffset)
|
||||
|
||||
def addr(dummy: Int = 0) =
|
||||
union(atosLenOffset - 1, atosAddrOffset)
|
||||
|
||||
def id(dummy: Int = 0) =
|
||||
union(atosIdBits - 1, 0)
|
||||
|
||||
def data(dummy: Int = 0) =
|
||||
union(atosStrobeOffset - 1, atosDataOffset)
|
||||
|
||||
def strb(dummy: Int = 0) =
|
||||
union(atosLastOffset - 1, atosStrobeOffset)
|
||||
|
||||
def last(dummy: Int = 0) =
|
||||
union(atosLastOffset)
|
||||
|
||||
def has_addr(dummy: Int = 0) =
|
||||
typ === AtosRequest.arType || typ === AtosRequest.awType
|
||||
|
||||
def has_data(dummy: Int = 0) =
|
||||
typ === AtosRequest.wType
|
||||
|
||||
def is_last(dummy: Int = 0) =
|
||||
typ === AtosRequest.arType || (typ === AtosRequest.wType && last())
|
||||
|
||||
def nbits: Int = atosRequestBits
|
||||
|
||||
def resp_len(dummy: Int = 0) =
|
||||
MuxLookup(typ, UInt(0), Seq(
|
||||
AtosRequest.arType -> (len() + UInt(1)),
|
||||
AtosRequest.awType -> UInt(1)))
|
||||
}
|
||||
|
||||
object AtosResponse {
|
||||
def rType = UInt("b00")
|
||||
def bType = UInt("b01")
|
||||
|
||||
def apply(typ: UInt, id: UInt, resp: UInt, data: UInt, last: Bool)
|
||||
(implicit p: Parameters): AtosResponse = {
|
||||
val aresp = Wire(new AtosResponse)
|
||||
aresp.typ := typ
|
||||
aresp.id := id
|
||||
aresp.resp := resp
|
||||
aresp.data := data
|
||||
aresp.last := last
|
||||
aresp
|
||||
}
|
||||
|
||||
def apply(r: NastiReadDataChannel)(implicit p: Parameters): AtosResponse =
|
||||
apply(rType, r.id, r.resp, r.data, r.last)
|
||||
|
||||
def apply(b: NastiWriteResponseChannel)(implicit p: Parameters): AtosResponse =
|
||||
apply(bType, b.id, b.resp, UInt(0), Bool(false))
|
||||
}
|
||||
|
||||
class AtosResponse(implicit p: Parameters)
|
||||
extends AtosBundle()(p) with Serializable {
|
||||
val typ = UInt(width = atosTypBits)
|
||||
val id = UInt(width = atosIdBits)
|
||||
val resp = UInt(width = atosRespBits)
|
||||
val last = Bool()
|
||||
val data = UInt(width = atosDataBits)
|
||||
|
||||
def has_data(dummy: Int = 0) = typ === AtosResponse.rType
|
||||
|
||||
def is_last(dummy: Int = 0) = !has_data() || last
|
||||
|
||||
def nbits: Int = atosResponseBits
|
||||
}
|
||||
|
||||
class AtosIO(implicit p: Parameters) extends AtosBundle()(p) {
|
||||
val req = Decoupled(new AtosRequest)
|
||||
val resp = Decoupled(new AtosResponse).flip
|
||||
}
|
||||
|
||||
class AtosRequestEncoder(implicit p: Parameters) extends AtosModule()(p) {
|
||||
val io = new Bundle {
|
||||
val ar = Decoupled(new NastiReadAddressChannel).flip
|
||||
val aw = Decoupled(new NastiWriteAddressChannel).flip
|
||||
val w = Decoupled(new NastiWriteDataChannel).flip
|
||||
val req = Decoupled(new AtosRequest)
|
||||
}
|
||||
|
||||
val writing = Reg(init = Bool(false))
|
||||
|
||||
io.ar.ready := !writing && io.req.ready
|
||||
io.aw.ready := !writing && !io.ar.valid && io.req.ready
|
||||
io.w.ready := writing && io.req.ready
|
||||
|
||||
io.req.valid := Mux(writing, io.w.valid, io.ar.valid || io.aw.valid)
|
||||
io.req.bits := Mux(writing, AtosRequest(io.w.bits),
|
||||
Mux(io.ar.valid, AtosRequest(io.ar.bits), AtosRequest(io.aw.bits)))
|
||||
|
||||
when (io.aw.fire()) { writing := Bool(true) }
|
||||
when (io.w.fire() && io.w.bits.last) { writing := Bool(false) }
|
||||
}
|
||||
|
||||
class AtosResponseDecoder(implicit p: Parameters) extends AtosModule()(p) {
|
||||
val io = new Bundle {
|
||||
val resp = Decoupled(new AtosResponse).flip
|
||||
val b = Decoupled(new NastiWriteResponseChannel)
|
||||
val r = Decoupled(new NastiReadDataChannel)
|
||||
}
|
||||
|
||||
val is_b = io.resp.bits.typ === AtosResponse.bType
|
||||
val is_r = io.resp.bits.typ === AtosResponse.rType
|
||||
|
||||
io.b.valid := io.resp.valid && is_b
|
||||
io.b.bits := NastiWriteResponseChannel(
|
||||
id = io.resp.bits.id,
|
||||
resp = io.resp.bits.resp)
|
||||
|
||||
io.r.valid := io.resp.valid && is_r
|
||||
io.r.bits := NastiReadDataChannel(
|
||||
id = io.resp.bits.id,
|
||||
data = io.resp.bits.data,
|
||||
last = io.resp.bits.last,
|
||||
resp = io.resp.bits.resp)
|
||||
|
||||
io.resp.ready := (is_b && io.b.ready) || (is_r && io.r.ready)
|
||||
}
|
||||
|
||||
class AtosClientConverter(implicit p: Parameters) extends AtosModule()(p) {
|
||||
val io = new Bundle {
|
||||
val nasti = (new NastiIO).flip
|
||||
val atos = new AtosIO
|
||||
}
|
||||
|
||||
val req_enc = Module(new AtosRequestEncoder)
|
||||
req_enc.io.ar <> io.nasti.ar
|
||||
req_enc.io.aw <> io.nasti.aw
|
||||
req_enc.io.w <> io.nasti.w
|
||||
io.atos.req <> req_enc.io.req
|
||||
|
||||
val resp_dec = Module(new AtosResponseDecoder)
|
||||
resp_dec.io.resp <> io.atos.resp
|
||||
io.nasti.b <> resp_dec.io.b
|
||||
io.nasti.r <> resp_dec.io.r
|
||||
}
|
||||
|
||||
class AtosRequestDecoder(implicit p: Parameters) extends AtosModule()(p) {
|
||||
val io = new Bundle {
|
||||
val req = Decoupled(new AtosRequest).flip
|
||||
val ar = Decoupled(new NastiReadAddressChannel)
|
||||
val aw = Decoupled(new NastiWriteAddressChannel)
|
||||
val w = Decoupled(new NastiWriteDataChannel)
|
||||
}
|
||||
|
||||
val is_ar = io.req.bits.typ === AtosRequest.arType
|
||||
val is_aw = io.req.bits.typ === AtosRequest.awType
|
||||
val is_w = io.req.bits.typ === AtosRequest.wType
|
||||
|
||||
io.ar.valid := io.req.valid && is_ar
|
||||
io.ar.bits := NastiReadAddressChannel(
|
||||
id = io.req.bits.id(),
|
||||
addr = io.req.bits.addr(),
|
||||
size = io.req.bits.size(),
|
||||
len = io.req.bits.len(),
|
||||
burst = io.req.bits.burst())
|
||||
|
||||
io.aw.valid := io.req.valid && is_aw
|
||||
io.aw.bits := NastiWriteAddressChannel(
|
||||
id = io.req.bits.id(),
|
||||
addr = io.req.bits.addr(),
|
||||
size = io.req.bits.size(),
|
||||
len = io.req.bits.len(),
|
||||
burst = io.req.bits.burst())
|
||||
|
||||
io.w.valid := io.req.valid && is_w
|
||||
io.w.bits := NastiWriteDataChannel(
|
||||
id = io.req.bits.id(),
|
||||
data = io.req.bits.data(),
|
||||
strb = Some(io.req.bits.strb()),
|
||||
last = io.req.bits.last())
|
||||
|
||||
io.req.ready := (io.ar.ready && is_ar) ||
|
||||
(io.aw.ready && is_aw) ||
|
||||
(io.w.ready && is_w)
|
||||
}
|
||||
|
||||
class AtosResponseEncoder(implicit p: Parameters) extends AtosModule()(p) {
|
||||
val io = new Bundle {
|
||||
val b = Decoupled(new NastiWriteResponseChannel).flip
|
||||
val r = Decoupled(new NastiReadDataChannel).flip
|
||||
val resp = Decoupled(new AtosResponse)
|
||||
}
|
||||
|
||||
val locked = Reg(init = Bool(false))
|
||||
|
||||
io.resp.valid := (io.b.valid && !locked) || io.r.valid
|
||||
io.resp.bits := Mux(io.r.valid,
|
||||
AtosResponse(io.r.bits), AtosResponse(io.b.bits))
|
||||
|
||||
io.b.ready := !locked && !io.r.valid && io.resp.ready
|
||||
io.r.ready := io.resp.ready
|
||||
|
||||
when (io.r.fire() && !io.r.bits.last) { locked := Bool(true) }
|
||||
when (io.r.fire() && io.r.bits.last) { locked := Bool(false) }
|
||||
}
|
||||
|
||||
class AtosManagerConverter(implicit p: Parameters) extends AtosModule()(p) {
|
||||
val io = new Bundle {
|
||||
val atos = (new AtosIO).flip
|
||||
val nasti = new NastiIO
|
||||
}
|
||||
|
||||
val req_dec = Module(new AtosRequestDecoder)
|
||||
val resp_enc = Module(new AtosResponseEncoder)
|
||||
|
||||
req_dec.io.req <> io.atos.req
|
||||
io.atos.resp <> resp_enc.io.resp
|
||||
|
||||
io.nasti.ar <> req_dec.io.ar
|
||||
io.nasti.aw <> req_dec.io.aw
|
||||
io.nasti.w <> req_dec.io.w
|
||||
|
||||
resp_enc.io.b <> io.nasti.b
|
||||
resp_enc.io.r <> io.nasti.r
|
||||
}
|
||||
|
||||
class AtosSerializedIO(w: Int)(implicit p: Parameters) extends ParameterizedBundle()(p) {
|
||||
val req = Decoupled(Bits(width = w))
|
||||
val resp = Decoupled(Bits(width = w)).flip
|
||||
val clk = Bool(OUTPUT)
|
||||
val clk_edge = Bool(OUTPUT)
|
||||
override def cloneType = new AtosSerializedIO(w)(p).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
class AtosSerdes(w: Int)(implicit p: Parameters) extends AtosModule()(p) {
|
||||
val io = new Bundle {
|
||||
val wide = (new AtosIO).flip
|
||||
val narrow = new AtosSerializedIO(w)
|
||||
}
|
||||
|
||||
val ser = Module(new Serializer(w, new AtosRequest))
|
||||
ser.io.in <> io.wide.req
|
||||
io.narrow.req <> ser.io.out
|
||||
|
||||
val des = Module(new Deserializer(w, new AtosResponse))
|
||||
des.io.in <> io.narrow.resp
|
||||
io.wide.resp <> des.io.out
|
||||
}
|
||||
|
||||
class AtosDesser(w: Int)(implicit p: Parameters) extends AtosModule()(p) {
|
||||
val io = new Bundle {
|
||||
val narrow = new AtosSerializedIO(w).flip
|
||||
val wide = new AtosIO
|
||||
}
|
||||
|
||||
val des = Module(new Deserializer(w, new AtosRequest))
|
||||
des.io.in <> io.narrow.req
|
||||
io.wide.req <> des.io.out
|
||||
|
||||
val ser = Module(new Serializer(w, new AtosResponse))
|
||||
ser.io.in <> io.wide.resp
|
||||
io.narrow.resp <> ser.io.out
|
||||
}
|
150
junctions/src/main/scala/crossing.scala
Normal file
150
junctions/src/main/scala/crossing.scala
Normal file
@ -0,0 +1,150 @@
|
||||
package junctions
|
||||
import Chisel._
|
||||
|
||||
class Crossing[T <: Data](gen: T, enq_sync: Boolean, deq_sync: Boolean) extends Bundle {
|
||||
val enq = Decoupled(gen).flip()
|
||||
val deq = Decoupled(gen)
|
||||
val enq_clock = if (enq_sync) Some(Clock(INPUT)) else None
|
||||
val deq_clock = if (deq_sync) Some(Clock(INPUT)) else None
|
||||
val enq_reset = if (enq_sync) Some(Bool(INPUT)) else None
|
||||
val deq_reset = if (deq_sync) Some(Bool(INPUT)) else None
|
||||
}
|
||||
|
||||
// Output is 1 for one cycle after any edge of 'in'
|
||||
object AsyncHandshakePulse {
|
||||
def apply(in: Bool, sync: Int): Bool = {
|
||||
val syncv = RegInit(Vec.fill(sync+1){Bool(false)})
|
||||
syncv.last := in
|
||||
(syncv.init zip syncv.tail).foreach { case (sink, source) => sink := source }
|
||||
syncv(0) =/= syncv(1)
|
||||
}
|
||||
}
|
||||
|
||||
class AsyncHandshakeSource[T <: Data](gen: T, sync: Int, clock: Clock, reset: Bool)
|
||||
extends Module(_clock = clock, _reset = reset) {
|
||||
val io = new Bundle {
|
||||
// These come from the source clock domain
|
||||
val enq = Decoupled(gen).flip()
|
||||
// These cross to the sink clock domain
|
||||
val bits = gen.cloneType.asOutput
|
||||
val push = Bool(OUTPUT)
|
||||
val pop = Bool(INPUT)
|
||||
}
|
||||
|
||||
val ready = RegInit(Bool(true))
|
||||
val bits = Reg(gen)
|
||||
val push = RegInit(Bool(false))
|
||||
|
||||
io.enq.ready := ready
|
||||
io.bits := bits
|
||||
io.push := push
|
||||
|
||||
val pop = AsyncHandshakePulse(io.pop, sync)
|
||||
assert (!pop || !ready)
|
||||
|
||||
when (pop) {
|
||||
ready := Bool(true)
|
||||
}
|
||||
|
||||
when (io.enq.fire()) {
|
||||
ready := Bool(false)
|
||||
bits := io.enq.bits
|
||||
push := !push
|
||||
}
|
||||
}
|
||||
|
||||
class AsyncHandshakeSink[T <: Data](gen: T, sync: Int, clock: Clock, reset: Bool)
|
||||
extends Module(_clock = clock, _reset = reset) {
|
||||
val io = new Bundle {
|
||||
// These cross to the source clock domain
|
||||
val bits = gen.cloneType.asInput
|
||||
val push = Bool(INPUT)
|
||||
val pop = Bool(OUTPUT)
|
||||
// These go to the sink clock domain
|
||||
val deq = Decoupled(gen)
|
||||
}
|
||||
|
||||
val valid = RegInit(Bool(false))
|
||||
val bits = Reg(gen)
|
||||
val pop = RegInit(Bool(false))
|
||||
|
||||
io.deq.valid := valid
|
||||
io.deq.bits := bits
|
||||
io.pop := pop
|
||||
|
||||
val push = AsyncHandshakePulse(io.push, sync)
|
||||
assert (!push || !valid)
|
||||
|
||||
when (push) {
|
||||
valid := Bool(true)
|
||||
bits := io.bits
|
||||
}
|
||||
|
||||
when (io.deq.fire()) {
|
||||
valid := Bool(false)
|
||||
pop := !pop
|
||||
}
|
||||
}
|
||||
|
||||
class AsyncHandshake[T <: Data](gen: T, sync: Int = 2) extends Module {
|
||||
val io = new Crossing(gen, true, true)
|
||||
require (sync >= 2)
|
||||
|
||||
val source = Module(new AsyncHandshakeSource(gen, sync, io.enq_clock.get, io.enq_reset.get))
|
||||
val sink = Module(new AsyncHandshakeSink (gen, sync, io.deq_clock.get, io.deq_reset.get))
|
||||
|
||||
source.io.enq <> io.enq
|
||||
io.deq <> sink.io.deq
|
||||
|
||||
sink.io.bits := source.io.bits
|
||||
sink.io.push := source.io.push
|
||||
source.io.pop := sink.io.pop
|
||||
}
|
||||
|
||||
class AsyncDecoupledTo[T <: Data](gen: T, depth: Int = 0, sync: Int = 2) extends Module {
|
||||
val io = new Crossing(gen, false, true)
|
||||
|
||||
// !!! if depth == 0 { use Handshake } else { use AsyncFIFO }
|
||||
val crossing = Module(new AsyncHandshake(gen, sync)).io
|
||||
crossing.enq_clock.get := clock
|
||||
crossing.enq_reset.get := reset
|
||||
crossing.enq <> io.enq
|
||||
crossing.deq_clock.get := io.deq_clock.get
|
||||
crossing.deq_reset.get := io.deq_reset.get
|
||||
io.deq <> crossing.deq
|
||||
}
|
||||
|
||||
object AsyncDecoupledTo {
|
||||
// source is in our clock domain, output is in the 'to' clock domain
|
||||
def apply[T <: Data](to_clock: Clock, to_reset: Bool, source: DecoupledIO[T], depth: Int = 0, sync: Int = 2): DecoupledIO[T] = {
|
||||
val to = Module(new AsyncDecoupledTo(source.bits, depth, sync))
|
||||
to.io.deq_clock.get := to_clock
|
||||
to.io.deq_reset.get := to_reset
|
||||
to.io.enq <> source
|
||||
to.io.deq
|
||||
}
|
||||
}
|
||||
|
||||
class AsyncDecoupledFrom[T <: Data](gen: T, depth: Int = 0, sync: Int = 2) extends Module {
|
||||
val io = new Crossing(gen, true, false)
|
||||
|
||||
// !!! if depth == 0 { use Handshake } else { use AsyncFIFO }
|
||||
val crossing = Module(new AsyncHandshake(gen, sync)).io
|
||||
crossing.enq_clock.get := io.enq_clock.get
|
||||
crossing.enq_reset.get := io.enq_reset.get
|
||||
crossing.enq <> io.enq
|
||||
crossing.deq_clock.get := clock
|
||||
crossing.deq_reset.get := reset
|
||||
io.deq <> crossing.deq
|
||||
}
|
||||
|
||||
object AsyncDecoupledFrom {
|
||||
// source is in the 'from' clock domain, output is in our clock domain
|
||||
def apply[T <: Data](from_clock: Clock, from_reset: Bool, source: DecoupledIO[T], depth: Int = 0, sync: Int = 2): DecoupledIO[T] = {
|
||||
val from = Module(new AsyncDecoupledFrom(source.bits, depth, sync))
|
||||
from.io.enq_clock.get := from_clock
|
||||
from.io.enq_reset.get := from_reset
|
||||
from.io.enq <> source
|
||||
from.io.deq
|
||||
}
|
||||
}
|
549
junctions/src/main/scala/hasti.scala
Normal file
549
junctions/src/main/scala/hasti.scala
Normal file
@ -0,0 +1,549 @@
|
||||
package junctions
|
||||
|
||||
import Chisel._
|
||||
import cde.{Parameters, Field}
|
||||
|
||||
object HastiConstants
|
||||
{
|
||||
// Values for htrans
|
||||
val SZ_HTRANS = 2
|
||||
val HTRANS_IDLE = UInt(0, SZ_HTRANS) // No transfer requested, not in a burst
|
||||
val HTRANS_BUSY = UInt(1, SZ_HTRANS) // No transfer requested, in a burst
|
||||
val HTRANS_NONSEQ = UInt(2, SZ_HTRANS) // First (potentially only) request in a burst
|
||||
val HTRANS_SEQ = UInt(3, SZ_HTRANS) // Following requests in a burst
|
||||
|
||||
// Values for hburst
|
||||
val SZ_HBURST = 3
|
||||
val HBURST_SINGLE = UInt(0, SZ_HBURST) // Single access (no burst)
|
||||
val HBURST_INCR = UInt(1, SZ_HBURST) // Incrementing burst of arbitrary length, not crossing 1KB
|
||||
val HBURST_WRAP4 = UInt(2, SZ_HBURST) // 4-beat wrapping burst
|
||||
val HBURST_INCR4 = UInt(3, SZ_HBURST) // 4-beat incrementing burst
|
||||
val HBURST_WRAP8 = UInt(4, SZ_HBURST) // 8-beat wrapping burst
|
||||
val HBURST_INCR8 = UInt(5, SZ_HBURST) // 8-beat incrementing burst
|
||||
val HBURST_WRAP16 = UInt(6, SZ_HBURST) // 16-beat wrapping burst
|
||||
val HBURST_INCR16 = UInt(7, SZ_HBURST) // 16-beat incrementing burst
|
||||
|
||||
// Values for hresp
|
||||
val SZ_HRESP = 1
|
||||
val HRESP_OKAY = UInt(0, SZ_HRESP)
|
||||
val HRESP_ERROR = UInt(1, SZ_HRESP)
|
||||
|
||||
// Values for hsize are identical to TileLink MT_SZ
|
||||
// ie: 8*2^SZ_HSIZE bit transfers
|
||||
val SZ_HSIZE = 3
|
||||
|
||||
// Values for hprot (a bitmask)
|
||||
val SZ_HPROT = 4
|
||||
def HPROT_DATA = UInt("b0001") // Data access or Opcode fetch
|
||||
def HPROT_PRIVILEGED = UInt("b0010") // Privileged or User access
|
||||
def HPROT_BUFFERABLE = UInt("b0100") // Bufferable or non-bufferable
|
||||
def HPROT_CACHEABLE = UInt("b1000") // Cacheable or non-cacheable
|
||||
|
||||
def dgate(valid: Bool, b: UInt) = Fill(b.getWidth, valid) & b
|
||||
}
|
||||
|
||||
import HastiConstants._
|
||||
|
||||
case class HastiParameters(dataBits: Int, addrBits: Int)
|
||||
case object HastiId extends Field[String]
|
||||
case class HastiKey(id: String) extends Field[HastiParameters]
|
||||
|
||||
trait HasHastiParameters {
|
||||
implicit val p: Parameters
|
||||
val hastiParams = p(HastiKey(p(HastiId)))
|
||||
val hastiAddrBits = hastiParams.addrBits
|
||||
val hastiDataBits = hastiParams.dataBits
|
||||
val hastiDataBytes = hastiDataBits/8
|
||||
val hastiAlignment = log2Ceil(hastiDataBytes)
|
||||
}
|
||||
|
||||
abstract class HastiModule(implicit val p: Parameters) extends Module
|
||||
with HasHastiParameters
|
||||
abstract class HastiBundle(implicit val p: Parameters) extends ParameterizedBundle()(p)
|
||||
with HasHastiParameters
|
||||
|
||||
class HastiMasterIO(implicit p: Parameters) extends HastiBundle()(p) {
|
||||
val htrans = UInt(OUTPUT, SZ_HTRANS)
|
||||
val hmastlock = Bool(OUTPUT)
|
||||
val haddr = UInt(OUTPUT, hastiAddrBits)
|
||||
val hwrite = Bool(OUTPUT)
|
||||
val hburst = UInt(OUTPUT, SZ_HBURST)
|
||||
val hsize = UInt(OUTPUT, SZ_HSIZE)
|
||||
val hprot = UInt(OUTPUT, SZ_HPROT)
|
||||
|
||||
val hwdata = Bits(OUTPUT, hastiDataBits)
|
||||
val hrdata = Bits(INPUT, hastiDataBits)
|
||||
|
||||
val hready = Bool(INPUT)
|
||||
val hresp = UInt(INPUT, SZ_HRESP)
|
||||
|
||||
def isNSeq(dummy:Int=0) = htrans === HTRANS_NONSEQ // SEQ does not start a NEW request
|
||||
def isHold(dummy:Int=0) = htrans === HTRANS_BUSY || htrans === HTRANS_SEQ
|
||||
def isIdle(dummy:Int=0) = htrans === HTRANS_IDLE || htrans === HTRANS_BUSY
|
||||
}
|
||||
|
||||
class HastiSlaveIO(implicit p: Parameters) extends HastiBundle()(p) {
|
||||
val htrans = UInt(INPUT, SZ_HTRANS)
|
||||
val hmastlock = Bool(INPUT)
|
||||
val haddr = UInt(INPUT, hastiAddrBits)
|
||||
val hwrite = Bool(INPUT)
|
||||
val hburst = UInt(INPUT, SZ_HBURST)
|
||||
val hsize = UInt(INPUT, SZ_HSIZE)
|
||||
val hprot = UInt(INPUT, SZ_HPROT)
|
||||
|
||||
val hwdata = Bits(INPUT, hastiDataBits)
|
||||
val hrdata = Bits(OUTPUT, hastiDataBits)
|
||||
|
||||
val hsel = Bool(INPUT)
|
||||
val hready = Bool(OUTPUT)
|
||||
val hresp = UInt(OUTPUT, SZ_HRESP)
|
||||
}
|
||||
|
||||
/* A diverted master is told hready when his address phase goes nowhere.
|
||||
* In this case, we buffer his address phase request and replay it later.
|
||||
* NOTE: this must optimize to nothing when divert is constantly false.
|
||||
*/
|
||||
class MasterDiversion(implicit p: Parameters) extends HastiModule()(p) {
|
||||
val io = new Bundle {
|
||||
val in = (new HastiMasterIO).flip
|
||||
val out = (new HastiMasterIO)
|
||||
val divert = Bool(INPUT)
|
||||
}
|
||||
|
||||
val full = Reg(init = Bool(false))
|
||||
val buffer = Reg(new HastiMasterIO)
|
||||
|
||||
when (io.out.hready) {
|
||||
full := Bool(false)
|
||||
}
|
||||
when (io.divert) {
|
||||
full := Bool(true)
|
||||
buffer := io.in
|
||||
}
|
||||
|
||||
// If the master is diverted, he must also have been told hready
|
||||
assert (!io.divert || io.in.hready,
|
||||
"Diverted but not ready");
|
||||
|
||||
// Replay the request we diverted
|
||||
io.out.htrans := Mux(full, buffer.htrans, io.in.htrans)
|
||||
io.out.hmastlock := Mux(full, buffer.hmastlock, io.in.hmastlock)
|
||||
io.out.haddr := Mux(full, buffer.haddr, io.in.haddr)
|
||||
io.out.hwrite := Mux(full, buffer.hwrite, io.in.hwrite)
|
||||
io.out.hburst := Mux(full, buffer.hburst, io.in.hburst)
|
||||
io.out.hsize := Mux(full, buffer.hsize, io.in.hsize)
|
||||
io.out.hprot := Mux(full, buffer.hprot, io.in.hprot)
|
||||
io.out.hwdata := Mux(full, buffer.hwdata, io.in.hwdata)
|
||||
|
||||
// Pass slave response back
|
||||
io.in.hrdata := io.out.hrdata
|
||||
io.in.hresp := io.out.hresp
|
||||
io.in.hready := io.out.hready && !full // Block master while we steal his address phase
|
||||
}
|
||||
|
||||
/* Masters with lower index have priority over higher index masters.
|
||||
* However, a lower priority master will retain control of a slave when EITHER:
|
||||
* 1. a burst is in progress (switching slaves mid-burst violates AHB-lite at slave)
|
||||
* 2. a transfer was waited (the standard forbids changing requests in this case)
|
||||
*
|
||||
* If a master raises hmastlock, it will be waited until no other master has inflight
|
||||
* requests; then, it acquires exclusive control of the crossbar until hmastlock is low.
|
||||
*
|
||||
* To implement an AHB-lite crossbar, it is important to realize that requests and
|
||||
* responses are coupled. Unlike modern bus protocols where the response data has flow
|
||||
* control independent of the request data, in AHB-lite, both flow at the same time at
|
||||
* the sole discretion of the slave via the hready signal. The address and data are
|
||||
* delivered on two back-to-back cycles, the so-called address and data phases.
|
||||
*
|
||||
* Masters can only be connected to a single slave at a time. If a master had two different
|
||||
* slave connections on the address and data phases, there would be two independent hready
|
||||
* signals. An AHB-lite slave can assume that data flows when it asserts hready. If the data
|
||||
* slave deasserts hready while the address slave asserts hready, the master is put in the
|
||||
* impossible position of being in data phase on two slaves at once. For this reason, when
|
||||
* a master issues back-to-back accesses to distinct slaves, we inject a pipeline bubble
|
||||
* between the two requests to limit the master to just a single slave at a time.
|
||||
*
|
||||
* Conversely, a slave CAN have two masters attached to it. This is unproblematic, because
|
||||
* the only signal which governs data flow is hready. Thus, both masters can be stalled
|
||||
* safely by the single slave.
|
||||
*/
|
||||
class HastiXbar(nMasters: Int, addressMap: Seq[UInt=>Bool])(implicit p: Parameters) extends HastiModule()(p) {
|
||||
val io = new Bundle {
|
||||
val masters = Vec(nMasters, new HastiMasterIO).flip
|
||||
val slaves = Vec(addressMap.size, new HastiSlaveIO).flip
|
||||
}
|
||||
|
||||
val nSlaves = addressMap.size
|
||||
|
||||
// Setup diversions infront of each master
|
||||
val diversions = Seq.tabulate(nMasters) { m => Module(new MasterDiversion) }
|
||||
(io.masters zip diversions) foreach { case (m, d) => d.io.in <> m }
|
||||
|
||||
// Handy short-hand
|
||||
val masters = diversions map (_.io.out)
|
||||
val slaves = io.slaves
|
||||
|
||||
// Lock status of the crossbar
|
||||
val lockedM = Reg(init = Vec.fill(nMasters)(Bool(false)))
|
||||
val isLocked = lockedM.reduce(_ || _)
|
||||
|
||||
// This matrix governs the master-slave connections in the address phase
|
||||
// It is indexed by addressPhaseGrantSM(slave)(master)
|
||||
// It is guaranteed to have at most one 'true' per column and per row
|
||||
val addressPhaseGrantSM = Wire(Vec(nSlaves, Vec(nMasters, Bool())))
|
||||
// This matrix governs the master-slave connections in the data phase
|
||||
// It is guaranteed to have at most one 'true' per column and per row
|
||||
val dataPhaseGrantSM = Reg (init = Vec.fill(nSlaves)(Vec.fill(nMasters)(Bool(false))))
|
||||
// This matrix is the union of the address and data phases.
|
||||
// It is transposed with respect to the two previous matrices.
|
||||
// It is guaranteed to contain at most one 'true' per master row.
|
||||
// However, two 'true's per slave column are permitted.
|
||||
val unionGrantMS = Vec.tabulate(nMasters) { m => Vec.tabulate(nSlaves) { s =>
|
||||
addressPhaseGrantSM(s)(m) || dataPhaseGrantSM(s)(m) } }
|
||||
|
||||
// Confirm the guarantees made above
|
||||
def justOnce(v: Vec[Bool]) = v.fold(Bool(false)) { case (p, v) =>
|
||||
assert (!p || !v)
|
||||
p || v
|
||||
}
|
||||
addressPhaseGrantSM foreach { s => justOnce(s) }
|
||||
unionGrantMS foreach { s => justOnce(s) }
|
||||
|
||||
// Data phase follows address phase whenever the slave is ready
|
||||
(slaves zip (dataPhaseGrantSM zip addressPhaseGrantSM)) foreach { case (s, (d, a)) =>
|
||||
when (s.hready) { d := a }
|
||||
}
|
||||
|
||||
// Record the grant state from the previous cycle; needed in case we hold access
|
||||
val priorAddressPhaseGrantSM = RegNext(addressPhaseGrantSM)
|
||||
|
||||
// If a master says BUSY or SEQ, it is in the middle of a burst.
|
||||
// In this case, it MUST stay attached to the same slave as before.
|
||||
// Otherwise, it would violate the AHB-lite specification as seen by
|
||||
// the slave, which is guaranteed a complete burst of the promised length.
|
||||
// One case where this matters is preventing preemption of low-prio masters.
|
||||
// NOTE: this exposes a slave to bad addresses when a master is buggy
|
||||
val holdBurstM = Vec(masters map { _.isHold() })
|
||||
|
||||
// Transform the burst hold requirement from master indexing to slave indexing
|
||||
// We use the previous cycle's binding because the master continues the prior burst
|
||||
val holdBurstS = Vec(priorAddressPhaseGrantSM map { m => Mux1H(m, holdBurstM) })
|
||||
|
||||
// If a slave says !hready to a request, it must retain the same master next cycle.
|
||||
// The AHB-lite specification requires that a waited transfer remain unchanged.
|
||||
// If we preempted a waited master, the new master's request could potentially differ.
|
||||
val holdBusyS = RegNext(Vec(slaves map { s => !s.hready && s.hsel }))
|
||||
|
||||
// Combine the above two grounds to determine if the slave retains its prior master
|
||||
val holdS = Vec((holdBurstS zip holdBusyS) map ({ case (a,b) => a||b }))
|
||||
|
||||
// Determine which master addresses match which slaves
|
||||
val matchMS = Vec(masters map { m => Vec(addressMap map { afn => afn(m.haddr) }) })
|
||||
// Detect requests to nowhere; we need to allow progress in this case
|
||||
val nowhereM = Vec(matchMS map { s => !s.reduce(_ || _) })
|
||||
|
||||
// Detect if we need to inject a pipeline bubble between the master requests.
|
||||
// Divert masters already granted a data phase different from next request.
|
||||
// NOTE: if only one slave, matchMS is always true => bubble always false
|
||||
// => the diversion registers are optimized away as they are unread
|
||||
// NOTE: bubble => dataPhase => have an hready signal
|
||||
val bubbleM =
|
||||
Vec.tabulate(nMasters) { m =>
|
||||
Vec.tabulate(nSlaves) { s => dataPhaseGrantSM(s)(m) && !matchMS(m)(s) }
|
||||
.reduce(_ || _) }
|
||||
|
||||
// Block any request that requires bus ownership or conflicts with isLocked
|
||||
val blockedM =
|
||||
Vec((lockedM zip masters) map { case(l, m) => !l && (isLocked || m.hmastlock) })
|
||||
|
||||
// Requested access to slaves from masters (pre-arbitration)
|
||||
// NOTE: isNSeq does NOT include SEQ; thus, masters who are midburst do not
|
||||
// request access to a new slave. They stay tied to the old and do not get two.
|
||||
// NOTE: if a master was waited, it must repeat the same request as last cycle;
|
||||
// thus, it will request the same slave and not end up with two (unless buggy).
|
||||
val NSeq = masters.map(_.isNSeq())
|
||||
val requestSM = Vec.tabulate(nSlaves) { s => Vec.tabulate(nMasters) { m =>
|
||||
matchMS(m)(s) && NSeq(m) && !bubbleM(m) && !blockedM(m) } }
|
||||
|
||||
// Select at most one master request per slave (lowest index = highest priority)
|
||||
val selectedRequestSM = Vec(requestSM map { m => Vec(PriorityEncoderOH(m)) })
|
||||
|
||||
// Calculate new crossbar interconnect state
|
||||
addressPhaseGrantSM := Vec((holdS zip (priorAddressPhaseGrantSM zip selectedRequestSM))
|
||||
map { case (h, (p, r)) => Mux(h, p, r) })
|
||||
|
||||
for (m <- 0 until nMasters) {
|
||||
// If the master is connected to a slave, the slave determines hready.
|
||||
// However, if no slave is connected, for progress report ready anyway, if:
|
||||
// bad address (swallow request) OR idle (permit stupid masters to move FSM)
|
||||
val autoready = nowhereM(m) || masters(m).isIdle()
|
||||
val hready = Mux1H(unionGrantMS(m), slaves.map(_.hready ^ autoready)) ^ autoready
|
||||
masters(m).hready := hready
|
||||
// If we diverted a master, we need to absorb his address phase to replay later
|
||||
diversions(m).io.divert := (bubbleM(m) || blockedM(m)) && NSeq(m) && hready
|
||||
}
|
||||
|
||||
// Master muxes (address and data phase are the same)
|
||||
(masters zip unionGrantMS) foreach { case (m, g) => {
|
||||
m.hrdata := Mux1H(g, slaves.map(_.hrdata))
|
||||
m.hresp := Mux1H(g, slaves.map(_.hresp))
|
||||
} }
|
||||
|
||||
// Slave address phase muxes
|
||||
(slaves zip addressPhaseGrantSM) foreach { case (s, g) => {
|
||||
s.htrans := Mux1H(g, masters.map(_.htrans))
|
||||
s.haddr := Mux1H(g, masters.map(_.haddr))
|
||||
s.hmastlock := isLocked
|
||||
s.hwrite := Mux1H(g, masters.map(_.hwrite))
|
||||
s.hsize := Mux1H(g, masters.map(_.hsize))
|
||||
s.hburst := Mux1H(g, masters.map(_.hburst))
|
||||
s.hprot := Mux1H(g, masters.map(_.hprot))
|
||||
s.hsel := g.reduce(_ || _)
|
||||
} }
|
||||
|
||||
// Slave data phase muxes
|
||||
(slaves zip dataPhaseGrantSM) foreach { case (s, g) => {
|
||||
s.hwdata := Mux1H(g, masters.map(_.hwdata))
|
||||
} }
|
||||
|
||||
// When no master-slave connections are active, a master can take-over the bus
|
||||
val canLock = !addressPhaseGrantSM.map({ v => v.reduce(_ || _) }).reduce(_ || _)
|
||||
|
||||
// Lowest index highest priority for lock arbitration
|
||||
val reqLock = masters.map(_.hmastlock)
|
||||
val winLock = PriorityEncoderOH(reqLock)
|
||||
|
||||
// Lock arbitration
|
||||
when (isLocked) {
|
||||
lockedM := (lockedM zip reqLock) map { case (a,b) => a && b }
|
||||
} .elsewhen (canLock) {
|
||||
lockedM := winLock
|
||||
}
|
||||
}
|
||||
|
||||
class HastiBus(amap: Seq[UInt=>Bool])(implicit p: Parameters) extends HastiModule()(p) {
|
||||
val io = new Bundle {
|
||||
val master = new HastiMasterIO().flip
|
||||
val slaves = Vec(amap.size, new HastiSlaveIO).flip
|
||||
}
|
||||
|
||||
val bar = Module(new HastiXbar(1, amap))
|
||||
bar.io.masters(0) <> io.master
|
||||
bar.io.slaves <> io.slaves
|
||||
}
|
||||
|
||||
class HastiSlaveMux(n: Int)(implicit p: Parameters) extends HastiModule()(p) {
|
||||
val io = new Bundle {
|
||||
val ins = Vec(n, new HastiSlaveIO)
|
||||
val out = new HastiSlaveIO().flip
|
||||
}
|
||||
|
||||
val amap = Seq({ (_:UInt) => Bool(true)})
|
||||
val bar = Module(new HastiXbar(n, amap))
|
||||
io.ins <> bar.io.masters
|
||||
io.out <> bar.io.slaves(0)
|
||||
}
|
||||
|
||||
class HastiSlaveToMaster(implicit p: Parameters) extends HastiModule()(p) {
|
||||
val io = new Bundle {
|
||||
val in = new HastiSlaveIO
|
||||
val out = new HastiMasterIO
|
||||
}
|
||||
|
||||
io.out.htrans := Mux(io.in.hsel, io.in.htrans, HTRANS_IDLE)
|
||||
io.out.hmastlock := io.in.hmastlock
|
||||
io.out.haddr := io.in.haddr
|
||||
io.out.hwrite := io.in.hwrite
|
||||
io.out.hburst := io.in.hburst
|
||||
io.out.hsize := io.in.hsize
|
||||
io.out.hprot := io.in.hprot
|
||||
io.out.hwdata := io.in.hwdata
|
||||
io.in.hrdata := io.out.hrdata
|
||||
io.in.hready := io.out.hready
|
||||
io.in.hresp := io.out.hresp
|
||||
}
|
||||
|
||||
class HastiMasterIONastiIOConverter(implicit p: Parameters) extends HastiModule()(p)
|
||||
with HasNastiParameters {
|
||||
val io = new Bundle {
|
||||
val nasti = new NastiIO().flip
|
||||
val hasti = new HastiMasterIO
|
||||
}
|
||||
|
||||
require(hastiAddrBits == nastiXAddrBits)
|
||||
require(hastiDataBits == nastiXDataBits)
|
||||
|
||||
val r_queue = Module(new Queue(new NastiReadDataChannel, 2, pipe = true))
|
||||
|
||||
val s_idle :: s_read :: s_write :: s_write_resp :: Nil = Enum(Bits(), 4)
|
||||
val state = Reg(init = s_idle)
|
||||
|
||||
val addr = Reg(UInt(width = hastiAddrBits))
|
||||
val id = Reg(UInt(width = nastiXIdBits))
|
||||
val size = Reg(UInt(width = nastiXSizeBits))
|
||||
val len = Reg(UInt(width = nastiXLenBits))
|
||||
val data = Reg(UInt(width = nastiXDataBits))
|
||||
val first = Reg(init = Bool(false))
|
||||
val is_rtrans = (state === s_read) &&
|
||||
(io.hasti.htrans === HTRANS_SEQ ||
|
||||
io.hasti.htrans === HTRANS_NONSEQ)
|
||||
val rvalid = RegEnable(is_rtrans, Bool(false), io.hasti.hready)
|
||||
|
||||
io.nasti.aw.ready := (state === s_idle)
|
||||
io.nasti.ar.ready := (state === s_idle) && !io.nasti.aw.valid
|
||||
io.nasti.w.ready := (state === s_write) && io.hasti.hready
|
||||
io.nasti.b.valid := (state === s_write_resp)
|
||||
io.nasti.b.bits := NastiWriteResponseChannel(id = id)
|
||||
io.nasti.r <> r_queue.io.deq
|
||||
|
||||
r_queue.io.enq.valid := io.hasti.hready && rvalid
|
||||
r_queue.io.enq.bits := NastiReadDataChannel(
|
||||
id = id,
|
||||
data = io.hasti.hrdata,
|
||||
last = (len === UInt(0)))
|
||||
|
||||
assert(!r_queue.io.enq.valid || r_queue.io.enq.ready,
|
||||
"NASTI -> HASTI converter queue overflow")
|
||||
|
||||
// How many read requests have we not delivered a response for yet?
|
||||
val pending_count = r_queue.io.count + rvalid
|
||||
|
||||
io.hasti.haddr := addr
|
||||
io.hasti.hsize := size
|
||||
io.hasti.hwrite := (state === s_write)
|
||||
io.hasti.hburst := HBURST_INCR
|
||||
io.hasti.hprot := UInt(0)
|
||||
io.hasti.hwdata := data
|
||||
io.hasti.hmastlock := Bool(false)
|
||||
io.hasti.htrans := MuxLookup(state, HTRANS_IDLE, Seq(
|
||||
s_write -> Mux(io.nasti.w.valid,
|
||||
Mux(first, HTRANS_NONSEQ, HTRANS_SEQ),
|
||||
Mux(first, HTRANS_IDLE, HTRANS_BUSY)),
|
||||
s_read -> MuxCase(HTRANS_BUSY, Seq(
|
||||
first -> HTRANS_NONSEQ,
|
||||
(pending_count <= UInt(1)) -> HTRANS_SEQ))))
|
||||
|
||||
when (io.nasti.aw.fire()) {
|
||||
first := Bool(true)
|
||||
addr := io.nasti.aw.bits.addr
|
||||
id := io.nasti.aw.bits.id
|
||||
size := io.nasti.aw.bits.size
|
||||
state := s_write
|
||||
}
|
||||
|
||||
when (io.nasti.ar.fire()) {
|
||||
first := Bool(true)
|
||||
addr := io.nasti.ar.bits.addr
|
||||
id := io.nasti.ar.bits.id
|
||||
size := io.nasti.ar.bits.size
|
||||
len := io.nasti.ar.bits.len
|
||||
state := s_read
|
||||
}
|
||||
|
||||
when (io.nasti.w.fire()) {
|
||||
first := Bool(false)
|
||||
addr := addr + (UInt(1) << size)
|
||||
data := io.nasti.w.bits.data
|
||||
when (io.nasti.w.bits.last) { state := s_write_resp }
|
||||
}
|
||||
|
||||
when (io.nasti.b.fire()) { state := s_idle }
|
||||
|
||||
when (is_rtrans && io.hasti.hready) {
|
||||
first := Bool(false)
|
||||
addr := addr + (UInt(1) << size)
|
||||
len := len - UInt(1)
|
||||
when (len === UInt(0)) { state := s_idle }
|
||||
}
|
||||
}
|
||||
|
||||
class HastiTestSRAM(depth: Int)(implicit p: Parameters) extends HastiModule()(p) {
|
||||
val io = new HastiSlaveIO
|
||||
|
||||
// This is a test SRAM with random delays
|
||||
val ready = LFSR16(Bool(true))(0) // Bool(true)
|
||||
|
||||
// Calculate the bitmask of which bytes are being accessed
|
||||
val mask_decode = Vec.tabulate(hastiAlignment+1) (UInt(_) <= io.hsize)
|
||||
val mask_wide = Vec.tabulate(hastiDataBytes) { i => mask_decode(log2Up(i+1)) }
|
||||
val mask_shift = if (hastiAlignment == 0) UInt(1) else
|
||||
mask_wide.toBits().asUInt() << io.haddr(hastiAlignment-1,0)
|
||||
|
||||
// The request had better have been aligned! (AHB-lite requires this)
|
||||
if (hastiAlignment >= 1) {
|
||||
assert (io.htrans === HTRANS_IDLE || io.htrans === HTRANS_BUSY ||
|
||||
(io.haddr & mask_decode.toBits()(hastiAlignment,1).asUInt) === UInt(0),
|
||||
"HASTI request not aligned")
|
||||
}
|
||||
|
||||
// The mask and address during the address phase
|
||||
val a_request = io.hsel && (io.htrans === HTRANS_NONSEQ || io.htrans === HTRANS_SEQ)
|
||||
val a_mask = Wire(UInt(width = hastiDataBytes))
|
||||
val a_address = io.haddr(depth-1, hastiAlignment)
|
||||
val a_write = io.hwrite
|
||||
|
||||
// for backwards compatibility with chisel2, we needed a static width in definition
|
||||
a_mask := mask_shift(hastiDataBytes-1, 0)
|
||||
|
||||
// The data phase signals
|
||||
val d_read = RegEnable(a_request && !a_write, Bool(false), ready)
|
||||
val d_mask = RegEnable(a_mask, ready && a_request)
|
||||
val d_wdata = Vec.tabulate(hastiDataBytes) { i => io.hwdata(8*(i+1)-1, 8*i) }
|
||||
|
||||
// AHB writes must occur during the data phase; this poses a structural
|
||||
// hazard with reads which must occur during the address phase. To solve
|
||||
// this problem, we delay the writes until there is a free cycle.
|
||||
//
|
||||
// The idea is to record the address information from address phase and
|
||||
// then as soon as possible flush the pending write. This cannot be done
|
||||
// on a cycle when there is an address phase read, but on any other cycle
|
||||
// the write will execute. In the case of reads following a write, the
|
||||
// result must bypass data from the pending write into the read if they
|
||||
// happen to have matching address.
|
||||
|
||||
// Remove this once HoldUnless is in chisel3
|
||||
def holdUnless[T <: Data](in : T, enable: Bool): T = Mux(!enable, RegEnable(in, enable), in)
|
||||
|
||||
// Pending write?
|
||||
val p_valid = RegInit(Bool(false))
|
||||
val p_address = Reg(a_address)
|
||||
val p_mask = Reg(a_mask)
|
||||
val p_latch_d = RegNext(ready && a_request && a_write, Bool(false))
|
||||
val p_wdata = holdUnless(d_wdata, p_latch_d)
|
||||
|
||||
// Use single-ported memory with byte-write enable
|
||||
val mem = SeqMem(1 << (depth-hastiAlignment), Vec(hastiDataBytes, Bits(width = 8)))
|
||||
|
||||
// Decide is the SRAM port is used for reading or (potentially) writing
|
||||
val read = ready && a_request && !a_write
|
||||
// In case we are stalled, we need to hold the read data
|
||||
val d_rdata = holdUnless(mem.read(a_address, read), RegNext(read))
|
||||
// Whenever the port is not needed for reading, execute pending writes
|
||||
when (!read) {
|
||||
when (p_valid) { mem.write(p_address, p_wdata, p_mask.toBools) }
|
||||
p_valid := Bool(false)
|
||||
}
|
||||
|
||||
// Record the request for later?
|
||||
when (ready && a_request && a_write) {
|
||||
p_valid := Bool(true)
|
||||
p_address := a_address
|
||||
p_mask := a_mask
|
||||
}
|
||||
|
||||
// Does the read need to be muxed with the previous write?
|
||||
val a_bypass = a_address === p_address && p_valid
|
||||
val d_bypass = RegEnable(a_bypass, ready && a_request)
|
||||
|
||||
// Mux in data from the pending write
|
||||
val muxdata = Vec((p_mask.toBools zip (p_wdata zip d_rdata))
|
||||
map { case (m, (p, r)) => Mux(d_bypass && m, p, r) })
|
||||
// Wipe out any data the master should not see (for testing)
|
||||
val outdata = Vec((d_mask.toBools zip muxdata)
|
||||
map { case (m, p) => Mux(d_read && ready && m, p, Bits(0)) })
|
||||
|
||||
// Finally, the outputs
|
||||
io.hrdata := outdata.toBits()
|
||||
io.hready := ready
|
||||
io.hresp := HRESP_OKAY
|
||||
}
|
317
junctions/src/main/scala/memserdes.scala
Normal file
317
junctions/src/main/scala/memserdes.scala
Normal file
@ -0,0 +1,317 @@
|
||||
// See LICENSE for license details.
|
||||
|
||||
package junctions
|
||||
import Chisel._
|
||||
import scala.math._
|
||||
import cde.{Parameters, Field}
|
||||
|
||||
case object MIFAddrBits extends Field[Int]
|
||||
case object MIFDataBits extends Field[Int]
|
||||
case object MIFTagBits extends Field[Int]
|
||||
case object MIFDataBeats extends Field[Int]
|
||||
|
||||
trait HasMIFParameters {
|
||||
implicit val p: Parameters
|
||||
val mifTagBits = p(MIFTagBits)
|
||||
val mifAddrBits = p(MIFAddrBits)
|
||||
val mifDataBits = p(MIFDataBits)
|
||||
val mifDataBeats = p(MIFDataBeats)
|
||||
}
|
||||
|
||||
abstract class MIFModule(implicit val p: Parameters) extends Module with HasMIFParameters
|
||||
abstract class MIFBundle(implicit val p: Parameters) extends ParameterizedBundle()(p)
|
||||
with HasMIFParameters
|
||||
|
||||
trait HasMemData extends HasMIFParameters {
|
||||
val data = Bits(width = mifDataBits)
|
||||
}
|
||||
|
||||
trait HasMemAddr extends HasMIFParameters {
|
||||
val addr = UInt(width = mifAddrBits)
|
||||
}
|
||||
|
||||
trait HasMemTag extends HasMIFParameters {
|
||||
val tag = UInt(width = mifTagBits)
|
||||
}
|
||||
|
||||
class MemReqCmd(implicit p: Parameters) extends MIFBundle()(p) with HasMemAddr with HasMemTag {
|
||||
val rw = Bool()
|
||||
}
|
||||
|
||||
class MemTag(implicit p: Parameters) extends MIFBundle()(p) with HasMemTag
|
||||
class MemData(implicit p: Parameters) extends MIFBundle()(p) with HasMemData
|
||||
class MemResp(implicit p: Parameters) extends MIFBundle()(p) with HasMemData with HasMemTag
|
||||
|
||||
class MemIO(implicit p: Parameters) extends ParameterizedBundle()(p) {
|
||||
val req_cmd = Decoupled(new MemReqCmd)
|
||||
val req_data = Decoupled(new MemData)
|
||||
val resp = Decoupled(new MemResp).flip
|
||||
}
|
||||
|
||||
class MemPipeIO(implicit p: Parameters) extends ParameterizedBundle()(p) {
|
||||
val req_cmd = Decoupled(new MemReqCmd)
|
||||
val req_data = Decoupled(new MemData)
|
||||
val resp = Valid(new MemResp).flip
|
||||
}
|
||||
|
||||
class MemSerializedIO(w: Int)(implicit p: Parameters) extends ParameterizedBundle()(p) {
|
||||
val req = Decoupled(Bits(width = w))
|
||||
val resp = Valid(Bits(width = w)).flip
|
||||
override def cloneType = new MemSerializedIO(w)(p).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
class MemSerdes(w: Int)(implicit p: Parameters) extends MIFModule
|
||||
{
|
||||
val io = new Bundle {
|
||||
val wide = new MemIO().flip
|
||||
val narrow = new MemSerializedIO(w)
|
||||
}
|
||||
val abits = io.wide.req_cmd.bits.toBits.getWidth
|
||||
val dbits = io.wide.req_data.bits.toBits.getWidth
|
||||
val rbits = io.wide.resp.bits.getWidth
|
||||
|
||||
val out_buf = Reg(Bits())
|
||||
val in_buf = Reg(Bits())
|
||||
|
||||
val s_idle :: s_read_addr :: s_write_addr :: s_write_idle :: s_write_data :: Nil = Enum(UInt(), 5)
|
||||
val state = Reg(init=s_idle)
|
||||
val send_cnt = Reg(init=UInt(0, log2Up((max(abits, dbits)+w-1)/w)))
|
||||
val data_send_cnt = Reg(init=UInt(0, log2Up(mifDataBeats)))
|
||||
val adone = io.narrow.req.ready && send_cnt === UInt((abits-1)/w)
|
||||
val ddone = io.narrow.req.ready && send_cnt === UInt((dbits-1)/w)
|
||||
|
||||
when (io.narrow.req.valid && io.narrow.req.ready) {
|
||||
send_cnt := send_cnt + UInt(1)
|
||||
out_buf := out_buf >> UInt(w)
|
||||
}
|
||||
when (io.wide.req_cmd.valid && io.wide.req_cmd.ready) {
|
||||
out_buf := io.wide.req_cmd.bits.toBits
|
||||
}
|
||||
when (io.wide.req_data.valid && io.wide.req_data.ready) {
|
||||
out_buf := io.wide.req_data.bits.toBits
|
||||
}
|
||||
|
||||
io.wide.req_cmd.ready := state === s_idle
|
||||
io.wide.req_data.ready := state === s_write_idle
|
||||
io.narrow.req.valid := state === s_read_addr || state === s_write_addr || state === s_write_data
|
||||
io.narrow.req.bits := out_buf
|
||||
|
||||
when (state === s_idle && io.wide.req_cmd.valid) {
|
||||
state := Mux(io.wide.req_cmd.bits.rw, s_write_addr, s_read_addr)
|
||||
}
|
||||
when (state === s_read_addr && adone) {
|
||||
state := s_idle
|
||||
send_cnt := UInt(0)
|
||||
}
|
||||
when (state === s_write_addr && adone) {
|
||||
state := s_write_idle
|
||||
send_cnt := UInt(0)
|
||||
}
|
||||
when (state === s_write_idle && io.wide.req_data.valid) {
|
||||
state := s_write_data
|
||||
}
|
||||
when (state === s_write_data && ddone) {
|
||||
data_send_cnt := data_send_cnt + UInt(1)
|
||||
state := Mux(data_send_cnt === UInt(mifDataBeats-1), s_idle, s_write_idle)
|
||||
send_cnt := UInt(0)
|
||||
}
|
||||
|
||||
val recv_cnt = Reg(init=UInt(0, log2Up((rbits+w-1)/w)))
|
||||
val data_recv_cnt = Reg(init=UInt(0, log2Up(mifDataBeats)))
|
||||
val resp_val = Reg(init=Bool(false))
|
||||
|
||||
resp_val := Bool(false)
|
||||
when (io.narrow.resp.valid) {
|
||||
recv_cnt := recv_cnt + UInt(1)
|
||||
when (recv_cnt === UInt((rbits-1)/w)) {
|
||||
recv_cnt := UInt(0)
|
||||
data_recv_cnt := data_recv_cnt + UInt(1)
|
||||
resp_val := Bool(true)
|
||||
}
|
||||
in_buf := Cat(io.narrow.resp.bits, in_buf((rbits+w-1)/w*w-1,w))
|
||||
}
|
||||
|
||||
io.wide.resp.valid := resp_val
|
||||
io.wide.resp.bits := io.wide.resp.bits.fromBits(in_buf)
|
||||
}
|
||||
|
||||
class MemDesserIO(w: Int)(implicit p: Parameters) extends ParameterizedBundle()(p) {
|
||||
val narrow = new MemSerializedIO(w).flip
|
||||
val wide = new MemIO
|
||||
}
|
||||
|
||||
class MemDesser(w: Int)(implicit p: Parameters) extends Module // test rig side
|
||||
{
|
||||
val io = new MemDesserIO(w)
|
||||
val abits = io.wide.req_cmd.bits.toBits.getWidth
|
||||
val dbits = io.wide.req_data.bits.toBits.getWidth
|
||||
val rbits = io.wide.resp.bits.getWidth
|
||||
val mifDataBeats = p(MIFDataBeats)
|
||||
|
||||
require(dbits >= abits && rbits >= dbits)
|
||||
val recv_cnt = Reg(init=UInt(0, log2Up((rbits+w-1)/w)))
|
||||
val data_recv_cnt = Reg(init=UInt(0, log2Up(mifDataBeats)))
|
||||
val adone = io.narrow.req.valid && recv_cnt === UInt((abits-1)/w)
|
||||
val ddone = io.narrow.req.valid && recv_cnt === UInt((dbits-1)/w)
|
||||
val rdone = io.narrow.resp.valid && recv_cnt === UInt((rbits-1)/w)
|
||||
|
||||
val s_cmd_recv :: s_cmd :: s_data_recv :: s_data :: s_reply :: Nil = Enum(UInt(), 5)
|
||||
val state = Reg(init=s_cmd_recv)
|
||||
|
||||
val in_buf = Reg(Bits())
|
||||
when (io.narrow.req.valid && io.narrow.req.ready || io.narrow.resp.valid) {
|
||||
recv_cnt := recv_cnt + UInt(1)
|
||||
in_buf := Cat(io.narrow.req.bits, in_buf((rbits+w-1)/w*w-1,w))
|
||||
}
|
||||
io.narrow.req.ready := state === s_cmd_recv || state === s_data_recv
|
||||
|
||||
when (state === s_cmd_recv && adone) {
|
||||
state := s_cmd
|
||||
recv_cnt := UInt(0)
|
||||
}
|
||||
when (state === s_cmd && io.wide.req_cmd.ready) {
|
||||
state := Mux(io.wide.req_cmd.bits.rw, s_data_recv, s_reply)
|
||||
}
|
||||
when (state === s_data_recv && ddone) {
|
||||
state := s_data
|
||||
recv_cnt := UInt(0)
|
||||
}
|
||||
when (state === s_data && io.wide.req_data.ready) {
|
||||
state := s_data_recv
|
||||
when (data_recv_cnt === UInt(mifDataBeats-1)) {
|
||||
state := s_cmd_recv
|
||||
}
|
||||
data_recv_cnt := data_recv_cnt + UInt(1)
|
||||
}
|
||||
when (rdone) { // state === s_reply
|
||||
when (data_recv_cnt === UInt(mifDataBeats-1)) {
|
||||
state := s_cmd_recv
|
||||
}
|
||||
recv_cnt := UInt(0)
|
||||
data_recv_cnt := data_recv_cnt + UInt(1)
|
||||
}
|
||||
|
||||
val req_cmd = in_buf >> UInt(((rbits+w-1)/w - (abits+w-1)/w)*w)
|
||||
io.wide.req_cmd.valid := state === s_cmd
|
||||
io.wide.req_cmd.bits := io.wide.req_cmd.bits.fromBits(req_cmd)
|
||||
|
||||
io.wide.req_data.valid := state === s_data
|
||||
io.wide.req_data.bits.data := in_buf >> UInt(((rbits+w-1)/w - (dbits+w-1)/w)*w)
|
||||
|
||||
val dataq = Module(new Queue(new MemResp, mifDataBeats))
|
||||
dataq.io.enq <> io.wide.resp
|
||||
dataq.io.deq.ready := recv_cnt === UInt((rbits-1)/w)
|
||||
|
||||
io.narrow.resp.valid := dataq.io.deq.valid
|
||||
io.narrow.resp.bits := dataq.io.deq.bits.toBits >> (recv_cnt * UInt(w))
|
||||
}
|
||||
|
||||
class MemIOArbiter(val arbN: Int)(implicit p: Parameters) extends MIFModule {
|
||||
val io = new Bundle {
|
||||
val inner = Vec(arbN, new MemIO).flip
|
||||
val outer = new MemIO
|
||||
}
|
||||
|
||||
if(arbN > 1) {
|
||||
val cmd_arb = Module(new RRArbiter(new MemReqCmd, arbN))
|
||||
val choice_q = Module(new Queue(cmd_arb.io.chosen, 4))
|
||||
val (data_cnt, data_done) = Counter(io.outer.req_data.fire(), mifDataBeats)
|
||||
|
||||
io.inner.map(_.req_cmd).zipWithIndex.zip(cmd_arb.io.in).map{ case ((req, id), arb) => {
|
||||
arb.valid := req.valid
|
||||
arb.bits := req.bits
|
||||
arb.bits.tag := Cat(req.bits.tag, UInt(id))
|
||||
req.ready := arb.ready
|
||||
}}
|
||||
io.outer.req_cmd.bits := cmd_arb.io.out.bits
|
||||
io.outer.req_cmd.valid := cmd_arb.io.out.valid && choice_q.io.enq.ready
|
||||
cmd_arb.io.out.ready := io.outer.req_cmd.ready && choice_q.io.enq.ready
|
||||
choice_q.io.enq.bits := cmd_arb.io.chosen
|
||||
choice_q.io.enq.valid := cmd_arb.io.out.fire() && cmd_arb.io.out.bits.rw
|
||||
|
||||
io.outer.req_data.bits := io.inner(choice_q.io.deq.bits).req_data.bits
|
||||
io.outer.req_data.valid := io.inner(choice_q.io.deq.bits).req_data.valid && choice_q.io.deq.valid
|
||||
io.inner.map(_.req_data.ready).zipWithIndex.foreach {
|
||||
case(r, i) => r := UInt(i) === choice_q.io.deq.bits && choice_q.io.deq.valid
|
||||
}
|
||||
choice_q.io.deq.ready := data_done
|
||||
|
||||
io.outer.resp.ready := Bool(false)
|
||||
for (i <- 0 until arbN) {
|
||||
io.inner(i).resp.valid := Bool(false)
|
||||
when(io.outer.resp.bits.tag(log2Up(arbN)-1,0).toUInt === UInt(i)) {
|
||||
io.inner(i).resp.valid := io.outer.resp.valid
|
||||
io.outer.resp.ready := io.inner(i).resp.ready
|
||||
}
|
||||
io.inner(i).resp.bits := io.outer.resp.bits
|
||||
io.inner(i).resp.bits.tag := io.outer.resp.bits.tag >> UInt(log2Up(arbN))
|
||||
}
|
||||
} else { io.outer <> io.inner.head }
|
||||
}
|
||||
|
||||
object MemIOMemPipeIOConverter {
|
||||
def apply(in: MemPipeIO)(implicit p: Parameters): MemIO = {
|
||||
val out = Wire(new MemIO())
|
||||
in.resp.valid := out.resp.valid
|
||||
in.resp.bits := out.resp.bits
|
||||
out.resp.ready := Bool(true)
|
||||
out.req_cmd.valid := in.req_cmd.valid
|
||||
out.req_cmd.bits := in.req_cmd.bits
|
||||
in.req_cmd.ready := out.req_cmd.ready
|
||||
out.req_data.valid := in.req_data.valid
|
||||
out.req_data.bits := in.req_data.bits
|
||||
in.req_data.ready := out.req_data.ready
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
class MemPipeIOMemIOConverter(numRequests: Int)(implicit p: Parameters) extends MIFModule {
|
||||
val io = new Bundle {
|
||||
val cpu = new MemIO().flip
|
||||
val mem = new MemPipeIO
|
||||
}
|
||||
|
||||
val numEntries = numRequests * mifDataBeats
|
||||
val size = log2Down(numEntries) + 1
|
||||
|
||||
val inc = Wire(Bool())
|
||||
val dec = Wire(Bool())
|
||||
val count = Reg(init=UInt(numEntries, size))
|
||||
val watermark = count >= UInt(mifDataBeats)
|
||||
|
||||
when (inc && !dec) {
|
||||
count := count + UInt(1)
|
||||
}
|
||||
when (!inc && dec) {
|
||||
count := count - UInt(mifDataBeats)
|
||||
}
|
||||
when (inc && dec) {
|
||||
count := count - UInt(mifDataBeats-1)
|
||||
}
|
||||
|
||||
val cmdq_mask = io.cpu.req_cmd.bits.rw || watermark
|
||||
|
||||
io.mem.req_cmd.valid := io.cpu.req_cmd.valid && cmdq_mask
|
||||
io.cpu.req_cmd.ready := io.mem.req_cmd.ready && cmdq_mask
|
||||
io.mem.req_cmd.bits := io.cpu.req_cmd.bits
|
||||
|
||||
io.mem.req_data <> io.cpu.req_data
|
||||
|
||||
// Have separate queues to allow for different mem implementations
|
||||
val resp_data_q = Module((new HellaQueue(numEntries)) { new MemData })
|
||||
resp_data_q.io.enq.valid := io.mem.resp.valid
|
||||
resp_data_q.io.enq.bits.data := io.mem.resp.bits.data
|
||||
|
||||
val resp_tag_q = Module((new HellaQueue(numEntries)) { new MemTag })
|
||||
resp_tag_q.io.enq.valid := io.mem.resp.valid
|
||||
resp_tag_q.io.enq.bits.tag := io.mem.resp.bits.tag
|
||||
|
||||
io.cpu.resp.valid := resp_data_q.io.deq.valid && resp_tag_q.io.deq.valid
|
||||
io.cpu.resp.bits.data := resp_data_q.io.deq.bits.data
|
||||
io.cpu.resp.bits.tag := resp_tag_q.io.deq.bits.tag
|
||||
resp_data_q.io.deq.ready := io.cpu.resp.ready
|
||||
resp_tag_q.io.deq.ready := io.cpu.resp.ready
|
||||
|
||||
inc := resp_data_q.io.deq.fire() && resp_tag_q.io.deq.fire()
|
||||
dec := io.mem.req_cmd.fire() && !io.mem.req_cmd.bits.rw
|
||||
}
|
737
junctions/src/main/scala/nasti.scala
Normal file
737
junctions/src/main/scala/nasti.scala
Normal file
@ -0,0 +1,737 @@
|
||||
/// See LICENSE for license details.
|
||||
|
||||
package junctions
|
||||
import Chisel._
|
||||
import scala.math.max
|
||||
import scala.collection.mutable.ArraySeq
|
||||
import cde.{Parameters, Field}
|
||||
|
||||
case object NastiKey extends Field[NastiParameters]
|
||||
|
||||
case class NastiParameters(dataBits: Int, addrBits: Int, idBits: Int)
|
||||
|
||||
trait HasNastiParameters {
|
||||
implicit val p: Parameters
|
||||
val nastiExternal = p(NastiKey)
|
||||
val nastiXDataBits = nastiExternal.dataBits
|
||||
val nastiWStrobeBits = nastiXDataBits / 8
|
||||
val nastiXAddrBits = nastiExternal.addrBits
|
||||
val nastiWIdBits = nastiExternal.idBits
|
||||
val nastiRIdBits = nastiExternal.idBits
|
||||
val nastiXIdBits = max(nastiWIdBits, nastiRIdBits)
|
||||
val nastiXUserBits = 1
|
||||
val nastiAWUserBits = nastiXUserBits
|
||||
val nastiWUserBits = nastiXUserBits
|
||||
val nastiBUserBits = nastiXUserBits
|
||||
val nastiARUserBits = nastiXUserBits
|
||||
val nastiRUserBits = nastiXUserBits
|
||||
val nastiXLenBits = 8
|
||||
val nastiXSizeBits = 3
|
||||
val nastiXBurstBits = 2
|
||||
val nastiXCacheBits = 4
|
||||
val nastiXProtBits = 3
|
||||
val nastiXQosBits = 4
|
||||
val nastiXRegionBits = 4
|
||||
val nastiXRespBits = 2
|
||||
|
||||
def bytesToXSize(bytes: UInt) = MuxLookup(bytes, UInt("b111"), Array(
|
||||
UInt(1) -> UInt(0),
|
||||
UInt(2) -> UInt(1),
|
||||
UInt(4) -> UInt(2),
|
||||
UInt(8) -> UInt(3),
|
||||
UInt(16) -> UInt(4),
|
||||
UInt(32) -> UInt(5),
|
||||
UInt(64) -> UInt(6),
|
||||
UInt(128) -> UInt(7)))
|
||||
}
|
||||
|
||||
abstract class NastiModule(implicit val p: Parameters) extends Module
|
||||
with HasNastiParameters
|
||||
abstract class NastiBundle(implicit val p: Parameters) extends ParameterizedBundle()(p)
|
||||
with HasNastiParameters
|
||||
|
||||
abstract class NastiChannel(implicit p: Parameters) extends NastiBundle()(p)
|
||||
abstract class NastiMasterToSlaveChannel(implicit p: Parameters) extends NastiChannel()(p)
|
||||
abstract class NastiSlaveToMasterChannel(implicit p: Parameters) extends NastiChannel()(p)
|
||||
|
||||
trait HasNastiMetadata extends HasNastiParameters {
|
||||
val addr = UInt(width = nastiXAddrBits)
|
||||
val len = UInt(width = nastiXLenBits)
|
||||
val size = UInt(width = nastiXSizeBits)
|
||||
val burst = UInt(width = nastiXBurstBits)
|
||||
val lock = Bool()
|
||||
val cache = UInt(width = nastiXCacheBits)
|
||||
val prot = UInt(width = nastiXProtBits)
|
||||
val qos = UInt(width = nastiXQosBits)
|
||||
val region = UInt(width = nastiXRegionBits)
|
||||
}
|
||||
|
||||
trait HasNastiData extends HasNastiParameters {
|
||||
val data = UInt(width = nastiXDataBits)
|
||||
val last = Bool()
|
||||
}
|
||||
|
||||
class NastiReadIO(implicit val p: Parameters) extends ParameterizedBundle()(p) {
|
||||
val ar = Decoupled(new NastiReadAddressChannel)
|
||||
val r = Decoupled(new NastiReadDataChannel).flip
|
||||
}
|
||||
|
||||
class NastiWriteIO(implicit val p: Parameters) extends ParameterizedBundle()(p) {
|
||||
val aw = Decoupled(new NastiWriteAddressChannel)
|
||||
val w = Decoupled(new NastiWriteDataChannel)
|
||||
val b = Decoupled(new NastiWriteResponseChannel).flip
|
||||
}
|
||||
|
||||
class NastiIO(implicit val p: Parameters) extends ParameterizedBundle()(p) {
|
||||
val aw = Decoupled(new NastiWriteAddressChannel)
|
||||
val w = Decoupled(new NastiWriteDataChannel)
|
||||
val b = Decoupled(new NastiWriteResponseChannel).flip
|
||||
val ar = Decoupled(new NastiReadAddressChannel)
|
||||
val r = Decoupled(new NastiReadDataChannel).flip
|
||||
}
|
||||
|
||||
class NastiAddressChannel(implicit p: Parameters) extends NastiMasterToSlaveChannel()(p)
|
||||
with HasNastiMetadata
|
||||
|
||||
class NastiResponseChannel(implicit p: Parameters) extends NastiSlaveToMasterChannel()(p) {
|
||||
val resp = UInt(width = nastiXRespBits)
|
||||
}
|
||||
|
||||
class NastiWriteAddressChannel(implicit p: Parameters) extends NastiAddressChannel()(p) {
|
||||
val id = UInt(width = nastiWIdBits)
|
||||
val user = UInt(width = nastiAWUserBits)
|
||||
}
|
||||
|
||||
class NastiWriteDataChannel(implicit p: Parameters) extends NastiMasterToSlaveChannel()(p)
|
||||
with HasNastiData {
|
||||
val id = UInt(width = nastiWIdBits)
|
||||
val strb = UInt(width = nastiWStrobeBits)
|
||||
val user = UInt(width = nastiWUserBits)
|
||||
}
|
||||
|
||||
class NastiWriteResponseChannel(implicit p: Parameters) extends NastiResponseChannel()(p) {
|
||||
val id = UInt(width = nastiWIdBits)
|
||||
val user = UInt(width = nastiBUserBits)
|
||||
}
|
||||
|
||||
class NastiReadAddressChannel(implicit p: Parameters) extends NastiAddressChannel()(p) {
|
||||
val id = UInt(width = nastiRIdBits)
|
||||
val user = UInt(width = nastiARUserBits)
|
||||
}
|
||||
|
||||
class NastiReadDataChannel(implicit p: Parameters) extends NastiResponseChannel()(p)
|
||||
with HasNastiData {
|
||||
val id = UInt(width = nastiRIdBits)
|
||||
val user = UInt(width = nastiRUserBits)
|
||||
}
|
||||
|
||||
object NastiConstants {
|
||||
val BURST_FIXED = UInt("b00")
|
||||
val BURST_INCR = UInt("b01")
|
||||
val BURST_WRAP = UInt("b10")
|
||||
|
||||
val RESP_OKAY = UInt("b00")
|
||||
val RESP_EXOKAY = UInt("b01")
|
||||
val RESP_SLVERR = UInt("b10")
|
||||
val RESP_DECERR = UInt("b11")
|
||||
}
|
||||
|
||||
import NastiConstants._
|
||||
|
||||
object NastiWriteAddressChannel {
|
||||
def apply(id: UInt, addr: UInt, size: UInt,
|
||||
len: UInt = UInt(0), burst: UInt = BURST_INCR)
|
||||
(implicit p: Parameters) = {
|
||||
val aw = Wire(new NastiWriteAddressChannel)
|
||||
aw.id := id
|
||||
aw.addr := addr
|
||||
aw.len := len
|
||||
aw.size := size
|
||||
aw.burst := burst
|
||||
aw.lock := Bool(false)
|
||||
aw.cache := UInt("b0000")
|
||||
aw.prot := UInt("b000")
|
||||
aw.qos := UInt("b0000")
|
||||
aw.region := UInt("b0000")
|
||||
aw.user := UInt(0)
|
||||
aw
|
||||
}
|
||||
}
|
||||
|
||||
object NastiReadAddressChannel {
|
||||
def apply(id: UInt, addr: UInt, size: UInt,
|
||||
len: UInt = UInt(0), burst: UInt = BURST_INCR)
|
||||
(implicit p: Parameters) = {
|
||||
val ar = Wire(new NastiReadAddressChannel)
|
||||
ar.id := id
|
||||
ar.addr := addr
|
||||
ar.len := len
|
||||
ar.size := size
|
||||
ar.burst := burst
|
||||
ar.lock := Bool(false)
|
||||
ar.cache := UInt(0)
|
||||
ar.prot := UInt(0)
|
||||
ar.qos := UInt(0)
|
||||
ar.region := UInt(0)
|
||||
ar.user := UInt(0)
|
||||
ar
|
||||
}
|
||||
}
|
||||
|
||||
object NastiWriteDataChannel {
|
||||
def apply(data: UInt, strb: Option[UInt] = None,
|
||||
last: Bool = Bool(true), id: UInt = UInt(0))
|
||||
(implicit p: Parameters): NastiWriteDataChannel = {
|
||||
val w = Wire(new NastiWriteDataChannel)
|
||||
w.strb := strb.getOrElse(Fill(w.nastiWStrobeBits, UInt(1, 1)))
|
||||
w.data := data
|
||||
w.last := last
|
||||
w.id := id
|
||||
w.user := UInt(0)
|
||||
w
|
||||
}
|
||||
}
|
||||
|
||||
object NastiReadDataChannel {
|
||||
def apply(id: UInt, data: UInt, last: Bool = Bool(true), resp: UInt = UInt(0))(
|
||||
implicit p: Parameters) = {
|
||||
val r = Wire(new NastiReadDataChannel)
|
||||
r.id := id
|
||||
r.data := data
|
||||
r.last := last
|
||||
r.resp := resp
|
||||
r.user := UInt(0)
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
object NastiWriteResponseChannel {
|
||||
def apply(id: UInt, resp: UInt = UInt(0))(implicit p: Parameters) = {
|
||||
val b = Wire(new NastiWriteResponseChannel)
|
||||
b.id := id
|
||||
b.resp := resp
|
||||
b.user := UInt(0)
|
||||
b
|
||||
}
|
||||
}
|
||||
|
||||
class MemIONastiIOConverter(cacheBlockOffsetBits: Int)(implicit p: Parameters) extends MIFModule
|
||||
with HasNastiParameters {
|
||||
val io = new Bundle {
|
||||
val nasti = (new NastiIO).flip
|
||||
val mem = new MemIO
|
||||
}
|
||||
|
||||
require(mifDataBits == nastiXDataBits, "Data sizes between LLC and MC don't agree")
|
||||
val (mif_cnt_out, mif_wrap_out) = Counter(io.mem.resp.fire(), mifDataBeats)
|
||||
|
||||
assert(!io.nasti.aw.valid || io.nasti.aw.bits.size === UInt(log2Up(mifDataBits/8)),
|
||||
"Nasti data size does not match MemIO data size")
|
||||
assert(!io.nasti.ar.valid || io.nasti.ar.bits.size === UInt(log2Up(mifDataBits/8)),
|
||||
"Nasti data size does not match MemIO data size")
|
||||
assert(!io.nasti.aw.valid || io.nasti.aw.bits.len === UInt(mifDataBeats - 1),
|
||||
"Nasti length does not match number of MemIO beats")
|
||||
assert(!io.nasti.ar.valid || io.nasti.ar.bits.len === UInt(mifDataBeats - 1),
|
||||
"Nasti length does not match number of MemIO beats")
|
||||
|
||||
// according to the spec, we can't send b until the last transfer on w
|
||||
val b_ok = Reg(init = Bool(true))
|
||||
when (io.nasti.aw.fire()) { b_ok := Bool(false) }
|
||||
when (io.nasti.w.fire() && io.nasti.w.bits.last) { b_ok := Bool(true) }
|
||||
|
||||
val id_q = Module(new Queue(UInt(width = nastiWIdBits), 2))
|
||||
id_q.io.enq.valid := io.nasti.aw.valid && io.mem.req_cmd.ready
|
||||
id_q.io.enq.bits := io.nasti.aw.bits.id
|
||||
id_q.io.deq.ready := io.nasti.b.ready && b_ok
|
||||
|
||||
io.mem.req_cmd.bits.addr := Mux(io.nasti.aw.valid, io.nasti.aw.bits.addr, io.nasti.ar.bits.addr) >>
|
||||
UInt(cacheBlockOffsetBits)
|
||||
io.mem.req_cmd.bits.tag := Mux(io.nasti.aw.valid, io.nasti.aw.bits.id, io.nasti.ar.bits.id)
|
||||
io.mem.req_cmd.bits.rw := io.nasti.aw.valid
|
||||
io.mem.req_cmd.valid := (io.nasti.aw.valid && id_q.io.enq.ready) || io.nasti.ar.valid
|
||||
io.nasti.ar.ready := io.mem.req_cmd.ready && !io.nasti.aw.valid
|
||||
io.nasti.aw.ready := io.mem.req_cmd.ready && id_q.io.enq.ready
|
||||
|
||||
io.nasti.b.valid := id_q.io.deq.valid && b_ok
|
||||
io.nasti.b.bits.id := id_q.io.deq.bits
|
||||
io.nasti.b.bits.resp := UInt(0)
|
||||
|
||||
io.nasti.w.ready := io.mem.req_data.ready
|
||||
io.mem.req_data.valid := io.nasti.w.valid
|
||||
io.mem.req_data.bits.data := io.nasti.w.bits.data
|
||||
assert(!io.nasti.w.valid || io.nasti.w.bits.strb.andR, "MemIO must write full cache line")
|
||||
|
||||
io.nasti.r.valid := io.mem.resp.valid
|
||||
io.nasti.r.bits.data := io.mem.resp.bits.data
|
||||
io.nasti.r.bits.last := mif_wrap_out
|
||||
io.nasti.r.bits.id := io.mem.resp.bits.tag
|
||||
io.nasti.r.bits.resp := UInt(0)
|
||||
io.mem.resp.ready := io.nasti.r.ready
|
||||
}
|
||||
|
||||
class NastiArbiterIO(arbN: Int)(implicit p: Parameters) extends Bundle {
|
||||
val master = Vec(arbN, new NastiIO).flip
|
||||
val slave = new NastiIO
|
||||
override def cloneType =
|
||||
new NastiArbiterIO(arbN).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
/** Arbitrate among arbN masters requesting to a single slave */
|
||||
class NastiArbiter(val arbN: Int)(implicit p: Parameters) extends NastiModule {
|
||||
val io = new NastiArbiterIO(arbN)
|
||||
|
||||
if (arbN > 1) {
|
||||
val arbIdBits = log2Up(arbN)
|
||||
|
||||
val ar_arb = Module(new RRArbiter(new NastiReadAddressChannel, arbN))
|
||||
val aw_arb = Module(new RRArbiter(new NastiWriteAddressChannel, arbN))
|
||||
|
||||
val slave_r_arb_id = io.slave.r.bits.id(arbIdBits - 1, 0)
|
||||
val slave_b_arb_id = io.slave.b.bits.id(arbIdBits - 1, 0)
|
||||
|
||||
val w_chosen = Reg(UInt(width = arbIdBits))
|
||||
val w_done = Reg(init = Bool(true))
|
||||
|
||||
when (aw_arb.io.out.fire()) {
|
||||
w_chosen := aw_arb.io.chosen
|
||||
w_done := Bool(false)
|
||||
}
|
||||
|
||||
when (io.slave.w.fire() && io.slave.w.bits.last) {
|
||||
w_done := Bool(true)
|
||||
}
|
||||
|
||||
for (i <- 0 until arbN) {
|
||||
val m_ar = io.master(i).ar
|
||||
val m_aw = io.master(i).aw
|
||||
val m_r = io.master(i).r
|
||||
val m_b = io.master(i).b
|
||||
val a_ar = ar_arb.io.in(i)
|
||||
val a_aw = aw_arb.io.in(i)
|
||||
val m_w = io.master(i).w
|
||||
|
||||
a_ar <> m_ar
|
||||
a_ar.bits.id := Cat(m_ar.bits.id, UInt(i, arbIdBits))
|
||||
|
||||
a_aw <> m_aw
|
||||
a_aw.bits.id := Cat(m_aw.bits.id, UInt(i, arbIdBits))
|
||||
|
||||
m_r.valid := io.slave.r.valid && slave_r_arb_id === UInt(i)
|
||||
m_r.bits := io.slave.r.bits
|
||||
m_r.bits.id := io.slave.r.bits.id >> UInt(arbIdBits)
|
||||
|
||||
m_b.valid := io.slave.b.valid && slave_b_arb_id === UInt(i)
|
||||
m_b.bits := io.slave.b.bits
|
||||
m_b.bits.id := io.slave.b.bits.id >> UInt(arbIdBits)
|
||||
|
||||
m_w.ready := io.slave.w.ready && w_chosen === UInt(i) && !w_done
|
||||
}
|
||||
|
||||
io.slave.r.ready := io.master(slave_r_arb_id).r.ready
|
||||
io.slave.b.ready := io.master(slave_b_arb_id).b.ready
|
||||
|
||||
io.slave.w.bits := io.master(w_chosen).w.bits
|
||||
io.slave.w.valid := io.master(w_chosen).w.valid && !w_done
|
||||
|
||||
io.slave.ar <> ar_arb.io.out
|
||||
|
||||
io.slave.aw.bits <> aw_arb.io.out.bits
|
||||
io.slave.aw.valid := aw_arb.io.out.valid && w_done
|
||||
aw_arb.io.out.ready := io.slave.aw.ready && w_done
|
||||
|
||||
} else { io.slave <> io.master.head }
|
||||
}
|
||||
|
||||
/** A slave that send decode error for every request it receives */
|
||||
class NastiErrorSlave(implicit p: Parameters) extends NastiModule {
|
||||
val io = (new NastiIO).flip
|
||||
|
||||
when (io.ar.fire()) { printf("Invalid read address %x\n", io.ar.bits.addr) }
|
||||
when (io.aw.fire()) { printf("Invalid write address %x\n", io.aw.bits.addr) }
|
||||
|
||||
val r_queue = Module(new Queue(new NastiReadAddressChannel, 1))
|
||||
r_queue.io.enq <> io.ar
|
||||
|
||||
val responding = Reg(init = Bool(false))
|
||||
val beats_left = Reg(init = UInt(0, nastiXLenBits))
|
||||
|
||||
when (!responding && r_queue.io.deq.valid) {
|
||||
responding := Bool(true)
|
||||
beats_left := r_queue.io.deq.bits.len
|
||||
}
|
||||
|
||||
io.r.valid := r_queue.io.deq.valid && responding
|
||||
io.r.bits.id := r_queue.io.deq.bits.id
|
||||
io.r.bits.data := UInt(0)
|
||||
io.r.bits.resp := RESP_DECERR
|
||||
io.r.bits.last := beats_left === UInt(0)
|
||||
|
||||
r_queue.io.deq.ready := io.r.fire() && io.r.bits.last
|
||||
|
||||
when (io.r.fire()) {
|
||||
when (beats_left === UInt(0)) {
|
||||
responding := Bool(false)
|
||||
} .otherwise {
|
||||
beats_left := beats_left - UInt(1)
|
||||
}
|
||||
}
|
||||
|
||||
val draining = Reg(init = Bool(false))
|
||||
io.w.ready := draining
|
||||
|
||||
when (io.aw.fire()) { draining := Bool(true) }
|
||||
when (io.w.fire() && io.w.bits.last) { draining := Bool(false) }
|
||||
|
||||
val b_queue = Module(new Queue(UInt(width = nastiWIdBits), 1))
|
||||
b_queue.io.enq.valid := io.aw.valid && !draining
|
||||
b_queue.io.enq.bits := io.aw.bits.id
|
||||
io.aw.ready := b_queue.io.enq.ready && !draining
|
||||
io.b.valid := b_queue.io.deq.valid && !draining
|
||||
io.b.bits.id := b_queue.io.deq.bits
|
||||
io.b.bits.resp := Bits("b11")
|
||||
b_queue.io.deq.ready := io.b.ready && !draining
|
||||
}
|
||||
|
||||
class NastiRouterIO(nSlaves: Int)(implicit p: Parameters) extends Bundle {
|
||||
val master = (new NastiIO).flip
|
||||
val slave = Vec(nSlaves, new NastiIO)
|
||||
override def cloneType =
|
||||
new NastiRouterIO(nSlaves).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
/** Take a single Nasti master and route its requests to various slaves
|
||||
* @param nSlaves the number of slaves
|
||||
* @param routeSel a function which takes an address and produces
|
||||
* a one-hot encoded selection of the slave to write to */
|
||||
class NastiRouter(nSlaves: Int, routeSel: UInt => UInt)(implicit p: Parameters)
|
||||
extends NastiModule {
|
||||
|
||||
val io = new NastiRouterIO(nSlaves)
|
||||
|
||||
val ar_route = routeSel(io.master.ar.bits.addr)
|
||||
val aw_route = routeSel(io.master.aw.bits.addr)
|
||||
|
||||
var ar_ready = Bool(false)
|
||||
var aw_ready = Bool(false)
|
||||
var w_ready = Bool(false)
|
||||
|
||||
io.slave.zipWithIndex.foreach { case (s, i) =>
|
||||
s.ar.valid := io.master.ar.valid && ar_route(i)
|
||||
s.ar.bits := io.master.ar.bits
|
||||
ar_ready = ar_ready || (s.ar.ready && ar_route(i))
|
||||
|
||||
s.aw.valid := io.master.aw.valid && aw_route(i)
|
||||
s.aw.bits := io.master.aw.bits
|
||||
aw_ready = aw_ready || (s.aw.ready && aw_route(i))
|
||||
|
||||
val chosen = Reg(init = Bool(false))
|
||||
when (s.w.fire() && s.w.bits.last) { chosen := Bool(false) }
|
||||
when (s.aw.fire()) { chosen := Bool(true) }
|
||||
|
||||
s.w.valid := io.master.w.valid && chosen
|
||||
s.w.bits := io.master.w.bits
|
||||
w_ready = w_ready || (s.w.ready && chosen)
|
||||
}
|
||||
|
||||
val r_invalid = !ar_route.orR
|
||||
val w_invalid = !aw_route.orR
|
||||
|
||||
val err_slave = Module(new NastiErrorSlave)
|
||||
err_slave.io.ar.valid := r_invalid && io.master.ar.valid
|
||||
err_slave.io.ar.bits := io.master.ar.bits
|
||||
err_slave.io.aw.valid := w_invalid && io.master.aw.valid
|
||||
err_slave.io.aw.bits := io.master.aw.bits
|
||||
err_slave.io.w.valid := io.master.w.valid
|
||||
err_slave.io.w.bits := io.master.w.bits
|
||||
|
||||
io.master.ar.ready := ar_ready || (r_invalid && err_slave.io.ar.ready)
|
||||
io.master.aw.ready := aw_ready || (w_invalid && err_slave.io.aw.ready)
|
||||
io.master.w.ready := w_ready || err_slave.io.w.ready
|
||||
|
||||
val b_arb = Module(new RRArbiter(new NastiWriteResponseChannel, nSlaves + 1))
|
||||
val r_arb = Module(new JunctionsPeekingArbiter(
|
||||
new NastiReadDataChannel, nSlaves + 1,
|
||||
// we can unlock if it's the last beat
|
||||
(r: NastiReadDataChannel) => r.last))
|
||||
|
||||
for (i <- 0 until nSlaves) {
|
||||
b_arb.io.in(i) <> io.slave(i).b
|
||||
r_arb.io.in(i) <> io.slave(i).r
|
||||
}
|
||||
|
||||
b_arb.io.in(nSlaves) <> err_slave.io.b
|
||||
r_arb.io.in(nSlaves) <> err_slave.io.r
|
||||
|
||||
io.master.b <> b_arb.io.out
|
||||
io.master.r <> r_arb.io.out
|
||||
}
|
||||
|
||||
/** Crossbar between multiple Nasti masters and slaves
|
||||
* @param nMasters the number of Nasti masters
|
||||
* @param nSlaves the number of Nasti slaves
|
||||
* @param routeSel a function selecting the slave to route an address to */
|
||||
class NastiCrossbar(nMasters: Int, nSlaves: Int, routeSel: UInt => UInt)
|
||||
(implicit p: Parameters) extends NastiModule {
|
||||
val io = new Bundle {
|
||||
val masters = Vec(nMasters, new NastiIO).flip
|
||||
val slaves = Vec(nSlaves, new NastiIO)
|
||||
}
|
||||
|
||||
if (nMasters == 1) {
|
||||
val router = Module(new NastiRouter(nSlaves, routeSel))
|
||||
router.io.master <> io.masters.head
|
||||
io.slaves <> router.io.slave
|
||||
} else {
|
||||
val routers = Vec.fill(nMasters) { Module(new NastiRouter(nSlaves, routeSel)).io }
|
||||
val arbiters = Vec.fill(nSlaves) { Module(new NastiArbiter(nMasters)).io }
|
||||
|
||||
for (i <- 0 until nMasters) {
|
||||
routers(i).master <> io.masters(i)
|
||||
}
|
||||
|
||||
for (i <- 0 until nSlaves) {
|
||||
arbiters(i).master <> Vec(routers.map(r => r.slave(i)))
|
||||
io.slaves(i) <> arbiters(i).slave
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NastiInterconnectIO(val nMasters: Int, val nSlaves: Int)
|
||||
(implicit p: Parameters) extends Bundle {
|
||||
/* This is a bit confusing. The interconnect is a slave to the masters and
|
||||
* a master to the slaves. Hence why the declarations seem to be backwards. */
|
||||
val masters = Vec(nMasters, new NastiIO).flip
|
||||
val slaves = Vec(nSlaves, new NastiIO)
|
||||
override def cloneType =
|
||||
new NastiInterconnectIO(nMasters, nSlaves).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
abstract class NastiInterconnect(implicit p: Parameters) extends NastiModule()(p) {
|
||||
val nMasters: Int
|
||||
val nSlaves: Int
|
||||
|
||||
lazy val io = new NastiInterconnectIO(nMasters, nSlaves)
|
||||
}
|
||||
|
||||
class NastiRecursiveInterconnect(val nMasters: Int, addrMap: AddrMap)
|
||||
(implicit p: Parameters) extends NastiInterconnect()(p) {
|
||||
def port(name: String) = io.slaves(addrMap.port(name))
|
||||
val nSlaves = addrMap.numSlaves
|
||||
val routeSel = (addr: UInt) =>
|
||||
Cat(addrMap.entries.map(e => addrMap(e.name).containsAddress(addr)).reverse)
|
||||
|
||||
val xbar = Module(new NastiCrossbar(nMasters, addrMap.length, routeSel))
|
||||
xbar.io.masters <> io.masters
|
||||
|
||||
io.slaves <> addrMap.entries.zip(xbar.io.slaves).flatMap {
|
||||
case (entry, xbarSlave) => {
|
||||
entry.region match {
|
||||
case submap: AddrMap if submap.entries.isEmpty =>
|
||||
val err_slave = Module(new NastiErrorSlave)
|
||||
err_slave.io <> xbarSlave
|
||||
None
|
||||
case submap: AddrMap =>
|
||||
val ic = Module(new NastiRecursiveInterconnect(1, submap))
|
||||
ic.io.masters.head <> xbarSlave
|
||||
ic.io.slaves
|
||||
case r: MemRange =>
|
||||
Some(xbarSlave)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelHelper(nChannels: Int)
|
||||
(implicit val p: Parameters) extends HasNastiParameters {
|
||||
|
||||
val dataBytes = p(MIFDataBits) * p(MIFDataBeats) / 8
|
||||
val chanSelBits = log2Ceil(nChannels)
|
||||
val selOffset = log2Up(dataBytes)
|
||||
val blockOffset = selOffset + chanSelBits
|
||||
|
||||
def getSelect(addr: UInt) =
|
||||
if (nChannels > 1) addr(blockOffset - 1, selOffset) else UInt(0)
|
||||
|
||||
def getAddr(addr: UInt) =
|
||||
if (nChannels > 1)
|
||||
Cat(addr(nastiXAddrBits - 1, blockOffset), addr(selOffset - 1, 0))
|
||||
else addr
|
||||
}
|
||||
|
||||
class NastiMemoryInterconnect(
|
||||
nBanksPerChannel: Int, nChannels: Int)
|
||||
(implicit p: Parameters) extends NastiInterconnect()(p) {
|
||||
|
||||
val nBanks = nBanksPerChannel * nChannels
|
||||
val nMasters = nBanks
|
||||
val nSlaves = nChannels
|
||||
|
||||
val chanHelper = new ChannelHelper(nChannels)
|
||||
def connectChannel(outer: NastiIO, inner: NastiIO) {
|
||||
outer <> inner
|
||||
outer.ar.bits.addr := chanHelper.getAddr(inner.ar.bits.addr)
|
||||
outer.aw.bits.addr := chanHelper.getAddr(inner.aw.bits.addr)
|
||||
}
|
||||
|
||||
for (i <- 0 until nChannels) {
|
||||
/* Bank assignments to channels are strided so that consecutive banks
|
||||
* map to different channels. That way, consecutive cache lines also
|
||||
* map to different channels */
|
||||
val banks = (i until nBanks by nChannels).map(j => io.masters(j))
|
||||
|
||||
val channelArb = Module(new NastiArbiter(nBanksPerChannel))
|
||||
channelArb.io.master <> banks
|
||||
connectChannel(io.slaves(i), channelArb.io.slave)
|
||||
}
|
||||
}
|
||||
|
||||
/** Allows users to switch between various memory configurations. Note that
|
||||
* this is a dangerous operation: not only does switching the select input to
|
||||
* this module violate Nasti, it also causes the memory of the machine to
|
||||
* become garbled. It's expected that select only changes at boot time, as
|
||||
* part of the memory controller configuration. */
|
||||
class NastiMemorySelectorIO(val nBanks: Int, val maxMemChannels: Int, nConfigs: Int)
|
||||
(implicit p: Parameters)
|
||||
extends NastiInterconnectIO(nBanks, maxMemChannels) {
|
||||
val select = UInt(INPUT, width = log2Up(nConfigs))
|
||||
override def cloneType =
|
||||
new NastiMemorySelectorIO(nMasters, nSlaves, nConfigs).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
class NastiMemorySelector(nBanks: Int, maxMemChannels: Int, configs: Seq[Int])
|
||||
(implicit p: Parameters)
|
||||
extends NastiInterconnect()(p) {
|
||||
val nMasters = nBanks
|
||||
val nSlaves = maxMemChannels
|
||||
val nConfigs = configs.size
|
||||
|
||||
override lazy val io = new NastiMemorySelectorIO(nBanks, maxMemChannels, nConfigs)
|
||||
|
||||
def muxOnSelect(up: DecoupledIO[Bundle], dn: DecoupledIO[Bundle], active: Bool): Unit = {
|
||||
when (active) { dn.bits := up.bits }
|
||||
when (active) { up.ready := dn.ready }
|
||||
when (active) { dn.valid := up.valid }
|
||||
}
|
||||
|
||||
def muxOnSelect(up: NastiIO, dn: NastiIO, active: Bool): Unit = {
|
||||
muxOnSelect(up.aw, dn.aw, active)
|
||||
muxOnSelect(up.w, dn.w, active)
|
||||
muxOnSelect(dn.b, up.b, active)
|
||||
muxOnSelect(up.ar, dn.ar, active)
|
||||
muxOnSelect(dn.r, up.r, active)
|
||||
}
|
||||
|
||||
def muxOnSelect(up: Vec[NastiIO], dn: Vec[NastiIO], active: Bool) : Unit = {
|
||||
for (i <- 0 until up.size)
|
||||
muxOnSelect(up(i), dn(i), active)
|
||||
}
|
||||
|
||||
/* Disconnects a vector of Nasti ports, which involves setting them to
|
||||
* invalid. Due to Chisel reasons, we need to also set the bits to 0 (since
|
||||
* there can't be any unconnected inputs). */
|
||||
def disconnectSlave(slave: Vec[NastiIO]) = {
|
||||
slave.foreach{ m =>
|
||||
m.aw.valid := Bool(false)
|
||||
m.aw.bits := m.aw.bits.fromBits( UInt(0) )
|
||||
m.w.valid := Bool(false)
|
||||
m.w.bits := m.w.bits.fromBits( UInt(0) )
|
||||
m.b.ready := Bool(false)
|
||||
m.ar.valid := Bool(false)
|
||||
m.ar.bits := m.ar.bits.fromBits( UInt(0) )
|
||||
m.r.ready := Bool(false)
|
||||
}
|
||||
}
|
||||
|
||||
def disconnectMaster(master: Vec[NastiIO]) = {
|
||||
master.foreach{ m =>
|
||||
m.aw.ready := Bool(false)
|
||||
m.w.ready := Bool(false)
|
||||
m.b.valid := Bool(false)
|
||||
m.b.bits := m.b.bits.fromBits( UInt(0) )
|
||||
m.ar.ready := Bool(false)
|
||||
m.r.valid := Bool(false)
|
||||
m.r.bits := m.r.bits.fromBits( UInt(0) )
|
||||
}
|
||||
}
|
||||
|
||||
/* Provides default wires on all our outputs. */
|
||||
disconnectMaster(io.masters)
|
||||
disconnectSlave(io.slaves)
|
||||
|
||||
/* Constructs interconnects for each of the layouts suggested by the
|
||||
* configuration and switches between them based on the select input. */
|
||||
configs.zipWithIndex.foreach{ case (nChannels, select) =>
|
||||
val nBanksPerChannel = nBanks / nChannels
|
||||
val ic = Module(new NastiMemoryInterconnect(nBanksPerChannel, nChannels))
|
||||
disconnectMaster(ic.io.slaves)
|
||||
disconnectSlave(ic.io.masters)
|
||||
muxOnSelect( io.masters, ic.io.masters, io.select === UInt(select))
|
||||
muxOnSelect(ic.io.slaves, io.slaves, io.select === UInt(select))
|
||||
}
|
||||
}
|
||||
|
||||
class NastiMemoryDemux(nRoutes: Int)(implicit p: Parameters) extends NastiModule()(p) {
|
||||
val io = new Bundle {
|
||||
val master = (new NastiIO).flip
|
||||
val slaves = Vec(nRoutes, new NastiIO)
|
||||
val select = UInt(INPUT, log2Up(nRoutes))
|
||||
}
|
||||
|
||||
def connectReqChannel[T <: Data](idx: Int, out: DecoupledIO[T], in: DecoupledIO[T]) {
|
||||
out.valid := in.valid && io.select === UInt(idx)
|
||||
out.bits := in.bits
|
||||
when (io.select === UInt(idx)) { in.ready := out.ready }
|
||||
}
|
||||
|
||||
def connectRespChannel[T <: Data](idx: Int, out: DecoupledIO[T], in: DecoupledIO[T]) {
|
||||
when (io.select === UInt(idx)) { out.valid := in.valid }
|
||||
when (io.select === UInt(idx)) { out.bits := in.bits }
|
||||
in.ready := out.ready && io.select === UInt(idx)
|
||||
}
|
||||
|
||||
io.master.ar.ready := Bool(false)
|
||||
io.master.aw.ready := Bool(false)
|
||||
io.master.w.ready := Bool(false)
|
||||
io.master.r.valid := Bool(false)
|
||||
io.master.r.bits := NastiReadDataChannel(id = UInt(0), data = UInt(0))
|
||||
io.master.b.valid := Bool(false)
|
||||
io.master.b.bits := NastiWriteResponseChannel(id = UInt(0))
|
||||
|
||||
io.slaves.zipWithIndex.foreach { case (slave, i) =>
|
||||
connectReqChannel(i, slave.ar, io.master.ar)
|
||||
connectReqChannel(i, slave.aw, io.master.aw)
|
||||
connectReqChannel(i, slave.w, io.master.w)
|
||||
connectRespChannel(i, io.master.r, slave.r)
|
||||
connectRespChannel(i, io.master.b, slave.b)
|
||||
}
|
||||
}
|
||||
|
||||
object AsyncNastiTo {
|
||||
// source(master) is in our clock domain, output is in the 'to' clock domain
|
||||
def apply[T <: Data](to_clock: Clock, to_reset: Bool, source: NastiIO, depth: Int = 3, sync: Int = 2)(implicit p: Parameters): NastiIO = {
|
||||
val sink = Wire(new NastiIO)
|
||||
|
||||
sink.aw <> AsyncDecoupledTo(to_clock, to_reset, source.aw, depth, sync)
|
||||
sink.ar <> AsyncDecoupledTo(to_clock, to_reset, source.ar, depth, sync)
|
||||
sink.w <> AsyncDecoupledTo(to_clock, to_reset, source.w, depth, sync)
|
||||
source.b <> AsyncDecoupledFrom(to_clock, to_reset, sink.b, depth, sync)
|
||||
source.r <> AsyncDecoupledFrom(to_clock, to_reset, sink.r, depth, sync)
|
||||
|
||||
sink
|
||||
}
|
||||
}
|
||||
|
||||
object AsyncNastiFrom {
|
||||
// source(master) is in the 'from' clock domain, output is in our clock domain
|
||||
def apply[T <: Data](from_clock: Clock, from_reset: Bool, source: NastiIO, depth: Int = 3, sync: Int = 2)(implicit p: Parameters): NastiIO = {
|
||||
val sink = Wire(new NastiIO)
|
||||
|
||||
sink.aw <> AsyncDecoupledFrom(from_clock, from_reset, source.aw, depth, sync)
|
||||
sink.ar <> AsyncDecoupledFrom(from_clock, from_reset, source.ar, depth, sync)
|
||||
sink.w <> AsyncDecoupledFrom(from_clock, from_reset, source.w, depth, sync)
|
||||
source.b <> AsyncDecoupledTo(from_clock, from_reset, sink.b, depth, sync)
|
||||
source.r <> AsyncDecoupledTo(from_clock, from_reset, sink.r, depth, sync)
|
||||
|
||||
sink
|
||||
}
|
||||
}
|
1
junctions/src/main/scala/package.scala
Normal file
1
junctions/src/main/scala/package.scala
Normal file
@ -0,0 +1 @@
|
||||
package object junctions
|
82
junctions/src/main/scala/poci.scala
Normal file
82
junctions/src/main/scala/poci.scala
Normal file
@ -0,0 +1,82 @@
|
||||
package junctions
|
||||
|
||||
import Chisel._
|
||||
import cde.{Parameters, Field}
|
||||
|
||||
class PociIO(implicit p: Parameters) extends HastiBundle()(p)
|
||||
{
|
||||
val paddr = UInt(OUTPUT, hastiAddrBits)
|
||||
val pwrite = Bool(OUTPUT)
|
||||
val psel = Bool(OUTPUT)
|
||||
val penable = Bool(OUTPUT)
|
||||
val pwdata = UInt(OUTPUT, hastiDataBits)
|
||||
val prdata = UInt(INPUT, hastiDataBits)
|
||||
val pready = Bool(INPUT)
|
||||
val pslverr = Bool(INPUT)
|
||||
}
|
||||
|
||||
class HastiToPociBridge(implicit p: Parameters) extends HastiModule()(p) {
|
||||
val io = new Bundle {
|
||||
val in = new HastiSlaveIO
|
||||
val out = new PociIO
|
||||
}
|
||||
|
||||
val s_idle :: s_setup :: s_access :: Nil = Enum(UInt(), 3)
|
||||
val state = Reg(init = s_idle)
|
||||
val transfer = io.in.hsel & io.in.htrans(1)
|
||||
|
||||
switch (state) {
|
||||
is (s_idle) {
|
||||
when (transfer) { state := s_setup }
|
||||
}
|
||||
is (s_setup) {
|
||||
state := s_access
|
||||
}
|
||||
is (s_access) {
|
||||
when (io.out.pready & ~transfer) { state := s_idle }
|
||||
when (io.out.pready & transfer) { state := s_setup }
|
||||
when (~io.out.pready) { state := s_access }
|
||||
}
|
||||
}
|
||||
|
||||
val haddr_reg = Reg(UInt(width = hastiAddrBits))
|
||||
val hwrite_reg = Reg(UInt(width = 1))
|
||||
when (transfer) {
|
||||
haddr_reg := io.in.haddr
|
||||
hwrite_reg := io.in.hwrite
|
||||
}
|
||||
|
||||
io.out.paddr := haddr_reg
|
||||
io.out.pwrite := hwrite_reg(0)
|
||||
io.out.psel := (state =/= s_idle)
|
||||
io.out.penable := (state === s_access)
|
||||
io.out.pwdata := io.in.hwdata
|
||||
io.in.hrdata := io.out.prdata
|
||||
io.in.hready := ((state === s_access) & io.out.pready) | (state === s_idle)
|
||||
io.in.hresp := io.out.pslverr
|
||||
}
|
||||
|
||||
class PociBus(amap: Seq[UInt=>Bool])(implicit p: Parameters) extends HastiModule()(p)
|
||||
{
|
||||
val io = new Bundle {
|
||||
val master = new PociIO().flip
|
||||
val slaves = Vec(amap.size, new PociIO)
|
||||
}
|
||||
|
||||
val psels = PriorityEncoderOH(
|
||||
(io.slaves zip amap) map { case (s, afn) => {
|
||||
s.paddr := io.master.paddr
|
||||
s.pwrite := io.master.pwrite
|
||||
s.pwdata := io.master.pwdata
|
||||
afn(io.master.paddr) && io.master.psel
|
||||
}})
|
||||
|
||||
(io.slaves zip psels) foreach { case (s, psel) => {
|
||||
s.psel := psel
|
||||
s.penable := io.master.penable && psel
|
||||
} }
|
||||
|
||||
io.master.prdata := Mux1H(psels, io.slaves.map(_.prdata))
|
||||
io.master.pready := Mux1H(psels, io.slaves.map(_.pready))
|
||||
io.master.pslverr := Mux1H(psels, io.slaves.map(_.pslverr))
|
||||
}
|
70
junctions/src/main/scala/slowio.scala
Normal file
70
junctions/src/main/scala/slowio.scala
Normal file
@ -0,0 +1,70 @@
|
||||
// See LICENSE for license details.
|
||||
|
||||
package junctions
|
||||
import Chisel._
|
||||
|
||||
class SlowIO[T <: Data](val divisor_max: Int)(data: => T) extends Module
|
||||
{
|
||||
val io = new Bundle {
|
||||
val out_fast = Decoupled(data).flip
|
||||
val out_slow = Decoupled(data)
|
||||
val in_fast = Decoupled(data)
|
||||
val in_slow = Decoupled(data).flip
|
||||
val clk_slow = Bool(OUTPUT)
|
||||
val set_divisor = Valid(Bits(width = 32)).flip
|
||||
val divisor = Bits(OUTPUT, 32)
|
||||
}
|
||||
|
||||
require(divisor_max >= 8 && divisor_max <= 65536 && isPow2(divisor_max))
|
||||
val divisor = Reg(init=UInt(divisor_max-1))
|
||||
val d_shadow = Reg(init=UInt(divisor_max-1))
|
||||
val hold = Reg(init=UInt(divisor_max/4-1))
|
||||
val h_shadow = Reg(init=UInt(divisor_max/4-1))
|
||||
when (io.set_divisor.valid) {
|
||||
d_shadow := io.set_divisor.bits(log2Up(divisor_max)-1, 0).toUInt
|
||||
h_shadow := io.set_divisor.bits(log2Up(divisor_max)-1+16, 16).toUInt
|
||||
}
|
||||
io.divisor := (hold << 16) | divisor
|
||||
|
||||
val count = Reg{UInt(width = log2Up(divisor_max))}
|
||||
val myclock = Reg{Bool()}
|
||||
count := count + UInt(1)
|
||||
|
||||
val rising = count === (divisor >> 1)
|
||||
val falling = count === divisor
|
||||
val held = count === (divisor >> 1) + hold
|
||||
|
||||
when (falling) {
|
||||
divisor := d_shadow
|
||||
hold := h_shadow
|
||||
count := UInt(0)
|
||||
myclock := Bool(false)
|
||||
}
|
||||
when (rising) {
|
||||
myclock := Bool(true)
|
||||
}
|
||||
|
||||
val in_slow_rdy = Reg(init=Bool(false))
|
||||
val out_slow_val = Reg(init=Bool(false))
|
||||
val out_slow_bits = Reg(data)
|
||||
|
||||
val fromhost_q = Module(new Queue(data,1))
|
||||
fromhost_q.io.enq.valid := rising && (io.in_slow.valid && in_slow_rdy || this.reset)
|
||||
fromhost_q.io.enq.bits := io.in_slow.bits
|
||||
io.in_fast <> fromhost_q.io.deq
|
||||
|
||||
val tohost_q = Module(new Queue(data,1))
|
||||
tohost_q.io.enq <> io.out_fast
|
||||
tohost_q.io.deq.ready := rising && io.out_slow.ready && out_slow_val
|
||||
|
||||
when (held) {
|
||||
in_slow_rdy := fromhost_q.io.enq.ready
|
||||
out_slow_val := tohost_q.io.deq.valid
|
||||
out_slow_bits := Mux(this.reset, fromhost_q.io.deq.bits, tohost_q.io.deq.bits)
|
||||
}
|
||||
|
||||
io.in_slow.ready := in_slow_rdy
|
||||
io.out_slow.valid := out_slow_val
|
||||
io.out_slow.bits := out_slow_bits
|
||||
io.clk_slow := myclock
|
||||
}
|
281
junctions/src/main/scala/smi.scala
Normal file
281
junctions/src/main/scala/smi.scala
Normal file
@ -0,0 +1,281 @@
|
||||
package junctions
|
||||
|
||||
import Chisel._
|
||||
import cde.Parameters
|
||||
|
||||
class SmiReq(val dataWidth: Int, val addrWidth: Int) extends Bundle {
|
||||
val rw = Bool()
|
||||
val addr = UInt(width = addrWidth)
|
||||
val data = Bits(width = dataWidth)
|
||||
|
||||
override def cloneType =
|
||||
new SmiReq(dataWidth, addrWidth).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
/** Simple Memory Interface IO. Used to communicate with PCR and SCR
|
||||
* @param dataWidth the width in bits of the data field
|
||||
* @param addrWidth the width in bits of the addr field */
|
||||
class SmiIO(val dataWidth: Int, val addrWidth: Int) extends Bundle {
|
||||
val req = Decoupled(new SmiReq(dataWidth, addrWidth))
|
||||
val resp = Decoupled(Bits(width = dataWidth)).flip
|
||||
|
||||
override def cloneType =
|
||||
new SmiIO(dataWidth, addrWidth).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
abstract class SmiPeripheral extends Module {
|
||||
val dataWidth: Int
|
||||
val addrWidth: Int
|
||||
|
||||
lazy val io = new SmiIO(dataWidth, addrWidth).flip
|
||||
}
|
||||
|
||||
/** A simple sequential memory accessed through Smi */
|
||||
class SmiMem(val dataWidth: Int, val memDepth: Int) extends SmiPeripheral {
|
||||
// override
|
||||
val addrWidth = log2Up(memDepth)
|
||||
|
||||
val mem = SeqMem(memDepth, Bits(width = dataWidth))
|
||||
|
||||
val ren = io.req.fire() && !io.req.bits.rw
|
||||
val wen = io.req.fire() && io.req.bits.rw
|
||||
|
||||
when (wen) { mem.write(io.req.bits.addr, io.req.bits.data) }
|
||||
|
||||
val resp_valid = Reg(init = Bool(false))
|
||||
|
||||
when (io.resp.fire()) { resp_valid := Bool(false) }
|
||||
when (io.req.fire()) { resp_valid := Bool(true) }
|
||||
|
||||
io.resp.valid := resp_valid
|
||||
io.resp.bits := mem.read(io.req.bits.addr, ren)
|
||||
io.req.ready := !resp_valid
|
||||
}
|
||||
|
||||
/** Arbitrate among several Smi clients
|
||||
* @param n the number of clients
|
||||
* @param dataWidth Smi data width
|
||||
* @param addrWidth Smi address width */
|
||||
class SmiArbiter(val n: Int, val dataWidth: Int, val addrWidth: Int)
|
||||
extends Module {
|
||||
val io = new Bundle {
|
||||
val in = Vec(n, new SmiIO(dataWidth, addrWidth)).flip
|
||||
val out = new SmiIO(dataWidth, addrWidth)
|
||||
}
|
||||
|
||||
val wait_resp = Reg(init = Bool(false))
|
||||
val choice = Reg(UInt(width = log2Up(n)))
|
||||
|
||||
val req_arb = Module(new RRArbiter(new SmiReq(dataWidth, addrWidth), n))
|
||||
req_arb.io.in <> io.in.map(_.req)
|
||||
req_arb.io.out.ready := io.out.req.ready && !wait_resp
|
||||
|
||||
io.out.req.bits := req_arb.io.out.bits
|
||||
io.out.req.valid := req_arb.io.out.valid && !wait_resp
|
||||
|
||||
when (io.out.req.fire()) {
|
||||
choice := req_arb.io.chosen
|
||||
wait_resp := Bool(true)
|
||||
}
|
||||
|
||||
when (io.out.resp.fire()) { wait_resp := Bool(false) }
|
||||
|
||||
for ((resp, i) <- io.in.map(_.resp).zipWithIndex) {
|
||||
resp.bits := io.out.resp.bits
|
||||
resp.valid := io.out.resp.valid && choice === UInt(i)
|
||||
}
|
||||
|
||||
io.out.resp.ready := io.in(choice).resp.ready
|
||||
}
|
||||
|
||||
class SmiIONastiReadIOConverter(val dataWidth: Int, val addrWidth: Int)
|
||||
(implicit p: Parameters) extends NastiModule()(p) {
|
||||
val io = new Bundle {
|
||||
val nasti = new NastiReadIO().flip
|
||||
val smi = new SmiIO(dataWidth, addrWidth)
|
||||
}
|
||||
|
||||
private val maxWordsPerBeat = nastiXDataBits / dataWidth
|
||||
private val wordCountBits = log2Up(maxWordsPerBeat)
|
||||
private val byteOffBits = log2Up(dataWidth / 8)
|
||||
private val addrOffBits = addrWidth + byteOffBits
|
||||
|
||||
private def calcWordCount(size: UInt): UInt =
|
||||
(UInt(1) << (size - UInt(byteOffBits))) - UInt(1)
|
||||
|
||||
val (s_idle :: s_read :: s_resp :: Nil) = Enum(Bits(), 3)
|
||||
val state = Reg(init = s_idle)
|
||||
|
||||
val nWords = Reg(UInt(width = wordCountBits))
|
||||
val nBeats = Reg(UInt(width = nastiXLenBits))
|
||||
val addr = Reg(UInt(width = addrWidth))
|
||||
val id = Reg(UInt(width = nastiRIdBits))
|
||||
|
||||
val byteOff = Reg(UInt(width = byteOffBits))
|
||||
val recvInd = Reg(init = UInt(0, wordCountBits))
|
||||
val sendDone = Reg(init = Bool(false))
|
||||
|
||||
val buffer = Reg(init = Vec.fill(maxWordsPerBeat) { Bits(0, dataWidth) })
|
||||
|
||||
io.nasti.ar.ready := (state === s_idle)
|
||||
|
||||
io.smi.req.valid := (state === s_read) && !sendDone
|
||||
io.smi.req.bits.rw := Bool(false)
|
||||
io.smi.req.bits.addr := addr
|
||||
|
||||
io.smi.resp.ready := (state === s_read)
|
||||
|
||||
io.nasti.r.valid := (state === s_resp)
|
||||
io.nasti.r.bits := NastiReadDataChannel(
|
||||
id = id,
|
||||
data = buffer.toBits,
|
||||
last = (nBeats === UInt(0)))
|
||||
|
||||
when (io.nasti.ar.fire()) {
|
||||
when (io.nasti.ar.bits.size < UInt(byteOffBits)) {
|
||||
nWords := UInt(0)
|
||||
} .otherwise {
|
||||
nWords := calcWordCount(io.nasti.ar.bits.size)
|
||||
}
|
||||
nBeats := io.nasti.ar.bits.len
|
||||
addr := io.nasti.ar.bits.addr(addrOffBits - 1, byteOffBits)
|
||||
if (maxWordsPerBeat > 1)
|
||||
recvInd := io.nasti.ar.bits.addr(wordCountBits + byteOffBits - 1, byteOffBits)
|
||||
else
|
||||
recvInd := UInt(0)
|
||||
id := io.nasti.ar.bits.id
|
||||
state := s_read
|
||||
}
|
||||
|
||||
when (io.smi.req.fire()) {
|
||||
addr := addr + UInt(1)
|
||||
sendDone := (nWords === UInt(0))
|
||||
}
|
||||
|
||||
when (io.smi.resp.fire()) {
|
||||
recvInd := recvInd + UInt(1)
|
||||
nWords := nWords - UInt(1)
|
||||
buffer(recvInd) := io.smi.resp.bits
|
||||
when (nWords === UInt(0)) { state := s_resp }
|
||||
}
|
||||
|
||||
when (io.nasti.r.fire()) {
|
||||
recvInd := UInt(0)
|
||||
sendDone := Bool(false)
|
||||
// clear all the registers in the buffer
|
||||
buffer.foreach(_ := Bits(0))
|
||||
nBeats := nBeats - UInt(1)
|
||||
state := Mux(io.nasti.r.bits.last, s_idle, s_read)
|
||||
}
|
||||
}
|
||||
|
||||
class SmiIONastiWriteIOConverter(val dataWidth: Int, val addrWidth: Int)
|
||||
(implicit p: Parameters) extends NastiModule()(p) {
|
||||
val io = new Bundle {
|
||||
val nasti = new NastiWriteIO().flip
|
||||
val smi = new SmiIO(dataWidth, addrWidth)
|
||||
}
|
||||
|
||||
private val dataBytes = dataWidth / 8
|
||||
private val maxWordsPerBeat = nastiXDataBits / dataWidth
|
||||
private val byteOffBits = log2Floor(dataBytes)
|
||||
private val addrOffBits = addrWidth + byteOffBits
|
||||
private val nastiByteOffBits = log2Ceil(nastiXDataBits / 8)
|
||||
|
||||
assert(!io.nasti.aw.valid || io.nasti.aw.bits.size >= UInt(byteOffBits),
|
||||
"Nasti size must be >= Smi size")
|
||||
|
||||
val id = Reg(UInt(width = nastiWIdBits))
|
||||
val addr = Reg(UInt(width = addrWidth))
|
||||
val offset = Reg(UInt(width = nastiByteOffBits))
|
||||
|
||||
def makeStrobe(offset: UInt, size: UInt, strb: UInt) = {
|
||||
val sizemask = (UInt(1) << (UInt(1) << size)) - UInt(1)
|
||||
val bytemask = strb & (sizemask << offset)
|
||||
Vec.tabulate(maxWordsPerBeat){i => bytemask(dataBytes * i)}.toBits
|
||||
}
|
||||
|
||||
val size = Reg(UInt(width = nastiXSizeBits))
|
||||
val strb = Reg(UInt(width = maxWordsPerBeat))
|
||||
val data = Reg(UInt(width = nastiXDataBits))
|
||||
val last = Reg(Bool())
|
||||
|
||||
val s_idle :: s_data :: s_send :: s_ack :: s_resp :: Nil = Enum(Bits(), 5)
|
||||
val state = Reg(init = s_idle)
|
||||
|
||||
io.nasti.aw.ready := (state === s_idle)
|
||||
io.nasti.w.ready := (state === s_data)
|
||||
io.smi.req.valid := (state === s_send) && strb(0)
|
||||
io.smi.req.bits.rw := Bool(true)
|
||||
io.smi.req.bits.addr := addr
|
||||
io.smi.req.bits.data := data(dataWidth - 1, 0)
|
||||
io.smi.resp.ready := (state === s_ack)
|
||||
io.nasti.b.valid := (state === s_resp)
|
||||
io.nasti.b.bits := NastiWriteResponseChannel(id)
|
||||
|
||||
val jump = if (maxWordsPerBeat > 1)
|
||||
PriorityMux(strb(maxWordsPerBeat - 1, 1),
|
||||
(1 until maxWordsPerBeat).map(UInt(_)))
|
||||
else UInt(1)
|
||||
|
||||
when (io.nasti.aw.fire()) {
|
||||
if (dataWidth == nastiXDataBits) {
|
||||
addr := io.nasti.aw.bits.addr(addrOffBits - 1, byteOffBits)
|
||||
} else {
|
||||
addr := Cat(io.nasti.aw.bits.addr(addrOffBits - 1, nastiByteOffBits),
|
||||
UInt(0, nastiByteOffBits - byteOffBits))
|
||||
}
|
||||
offset := io.nasti.aw.bits.addr(nastiByteOffBits - 1, 0)
|
||||
id := io.nasti.aw.bits.id
|
||||
size := io.nasti.aw.bits.size
|
||||
last := Bool(false)
|
||||
state := s_data
|
||||
}
|
||||
|
||||
when (io.nasti.w.fire()) {
|
||||
last := io.nasti.w.bits.last
|
||||
strb := makeStrobe(offset, size, io.nasti.w.bits.strb)
|
||||
data := io.nasti.w.bits.data
|
||||
state := s_send
|
||||
}
|
||||
|
||||
when (state === s_send) {
|
||||
when (io.smi.req.ready || !strb(0)) {
|
||||
strb := strb >> jump
|
||||
data := data >> Cat(jump, UInt(0, log2Up(dataWidth)))
|
||||
addr := addr + jump
|
||||
when (strb(0)) { state := s_ack }
|
||||
}
|
||||
}
|
||||
|
||||
when (io.smi.resp.fire()) {
|
||||
state := Mux(strb === UInt(0),
|
||||
Mux(last, s_resp, s_data), s_send)
|
||||
}
|
||||
|
||||
when (io.nasti.b.fire()) { state := s_idle }
|
||||
}
|
||||
|
||||
/** Convert Nasti protocol to Smi protocol */
|
||||
class SmiIONastiIOConverter(val dataWidth: Int, val addrWidth: Int)
|
||||
(implicit p: Parameters) extends NastiModule()(p) {
|
||||
val io = new Bundle {
|
||||
val nasti = (new NastiIO).flip
|
||||
val smi = new SmiIO(dataWidth, addrWidth)
|
||||
}
|
||||
|
||||
require(isPow2(dataWidth), "SMI data width must be power of 2")
|
||||
require(dataWidth <= nastiXDataBits,
|
||||
"SMI data width must be less than or equal to NASTI data width")
|
||||
|
||||
val reader = Module(new SmiIONastiReadIOConverter(dataWidth, addrWidth))
|
||||
reader.io.nasti <> io.nasti
|
||||
|
||||
val writer = Module(new SmiIONastiWriteIOConverter(dataWidth, addrWidth))
|
||||
writer.io.nasti <> io.nasti
|
||||
|
||||
val arb = Module(new SmiArbiter(2, dataWidth, addrWidth))
|
||||
arb.io.in(0) <> reader.io.smi
|
||||
arb.io.in(1) <> writer.io.smi
|
||||
io.smi <> arb.io.out
|
||||
}
|
187
junctions/src/main/scala/stream.scala
Normal file
187
junctions/src/main/scala/stream.scala
Normal file
@ -0,0 +1,187 @@
|
||||
package junctions
|
||||
|
||||
import Chisel._
|
||||
import NastiConstants._
|
||||
import cde.Parameters
|
||||
|
||||
class StreamChannel(w: Int) extends Bundle {
|
||||
val data = UInt(width = w)
|
||||
val last = Bool()
|
||||
|
||||
override def cloneType = new StreamChannel(w).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
class StreamIO(w: Int) extends Bundle {
|
||||
val out = Decoupled(new StreamChannel(w))
|
||||
val in = Decoupled(new StreamChannel(w)).flip
|
||||
|
||||
override def cloneType = new StreamIO(w).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
class NastiIOStreamIOConverter(w: Int)(implicit p: Parameters) extends Module {
|
||||
val io = new Bundle {
|
||||
val nasti = (new NastiIO).flip
|
||||
val stream = new StreamIO(w)
|
||||
}
|
||||
|
||||
val streamSize = UInt(log2Up(w / 8))
|
||||
assert(!io.nasti.ar.valid || io.nasti.ar.bits.size === streamSize,
|
||||
"read channel wrong size on stream")
|
||||
assert(!io.nasti.ar.valid || io.nasti.ar.bits.len === UInt(0) ||
|
||||
io.nasti.ar.bits.burst === BURST_FIXED,
|
||||
"read channel wrong burst type on stream")
|
||||
assert(!io.nasti.aw.valid || io.nasti.aw.bits.size === streamSize,
|
||||
"write channel wrong size on stream")
|
||||
assert(!io.nasti.aw.valid || io.nasti.aw.bits.len === UInt(0) ||
|
||||
io.nasti.aw.bits.burst === BURST_FIXED,
|
||||
"write channel wrong burst type on stream")
|
||||
assert(!io.nasti.w.valid || io.nasti.w.bits.strb.andR,
|
||||
"write channel cannot take partial writes")
|
||||
|
||||
val read_id = Reg(io.nasti.ar.bits.id)
|
||||
val read_cnt = Reg(io.nasti.ar.bits.len)
|
||||
val reading = Reg(init = Bool(false))
|
||||
|
||||
io.nasti.ar.ready := !reading
|
||||
io.nasti.r.valid := reading && io.stream.in.valid
|
||||
io.nasti.r.bits := io.stream.in.bits
|
||||
io.nasti.r.bits.resp := UInt(0)
|
||||
io.nasti.r.bits.id := read_id
|
||||
io.stream.in.ready := reading && io.nasti.r.ready
|
||||
|
||||
when (io.nasti.ar.fire()) {
|
||||
read_id := io.nasti.ar.bits.id
|
||||
read_cnt := io.nasti.ar.bits.len
|
||||
reading := Bool(true)
|
||||
}
|
||||
|
||||
when (io.nasti.r.fire()) {
|
||||
when (read_cnt === UInt(0)) {
|
||||
reading := Bool(false)
|
||||
} .otherwise {
|
||||
read_cnt := read_cnt - UInt(1)
|
||||
}
|
||||
}
|
||||
|
||||
val write_id = Reg(io.nasti.aw.bits.id)
|
||||
val writing = Reg(init = Bool(false))
|
||||
val write_resp = Reg(init = Bool(false))
|
||||
|
||||
io.nasti.aw.ready := !writing && !write_resp
|
||||
io.nasti.w.ready := writing && io.stream.out.ready
|
||||
io.stream.out.valid := writing && io.nasti.w.valid
|
||||
io.stream.out.bits := io.nasti.w.bits
|
||||
io.nasti.b.valid := write_resp
|
||||
io.nasti.b.bits.resp := UInt(0)
|
||||
io.nasti.b.bits.id := write_id
|
||||
|
||||
when (io.nasti.aw.fire()) {
|
||||
write_id := io.nasti.aw.bits.id
|
||||
writing := Bool(true)
|
||||
}
|
||||
|
||||
when (io.nasti.w.fire() && io.nasti.w.bits.last) {
|
||||
writing := Bool(false)
|
||||
write_resp := Bool(true)
|
||||
}
|
||||
|
||||
when (io.nasti.b.fire()) { write_resp := Bool(false) }
|
||||
}
|
||||
|
||||
class StreamNarrower(win: Int, wout: Int) extends Module {
|
||||
require(win > wout, "Stream narrower input width must be larger than input width")
|
||||
require(win % wout == 0, "Stream narrower input width must be multiple of output width")
|
||||
|
||||
val io = new Bundle {
|
||||
val in = Decoupled(new StreamChannel(win)).flip
|
||||
val out = Decoupled(new StreamChannel(wout))
|
||||
}
|
||||
|
||||
val n_pieces = win / wout
|
||||
val buffer = Reg(Bits(width = win))
|
||||
val (piece_idx, pkt_done) = Counter(io.out.fire(), n_pieces)
|
||||
val pieces = Vec.tabulate(n_pieces) { i => buffer(wout * (i + 1) - 1, wout * i) }
|
||||
val last_piece = (piece_idx === UInt(n_pieces - 1))
|
||||
val sending = Reg(init = Bool(false))
|
||||
val in_last = Reg(Bool())
|
||||
|
||||
when (io.in.fire()) {
|
||||
buffer := io.in.bits.data
|
||||
in_last := io.in.bits.last
|
||||
sending := Bool(true)
|
||||
}
|
||||
when (pkt_done) { sending := Bool(false) }
|
||||
|
||||
io.out.valid := sending
|
||||
io.out.bits.data := pieces(piece_idx)
|
||||
io.out.bits.last := in_last && last_piece
|
||||
io.in.ready := !sending
|
||||
}
|
||||
|
||||
class StreamExpander(win: Int, wout: Int) extends Module {
|
||||
require(win < wout, "Stream expander input width must be smaller than input width")
|
||||
require(wout % win == 0, "Stream narrower output width must be multiple of input width")
|
||||
|
||||
val io = new Bundle {
|
||||
val in = Decoupled(new StreamChannel(win)).flip
|
||||
val out = Decoupled(new StreamChannel(wout))
|
||||
}
|
||||
|
||||
val n_pieces = wout / win
|
||||
val buffer = Reg(Vec(n_pieces, UInt(width = win)))
|
||||
val last = Reg(Bool())
|
||||
val collecting = Reg(init = Bool(true))
|
||||
val (piece_idx, pkt_done) = Counter(io.in.fire(), n_pieces)
|
||||
|
||||
when (io.in.fire()) { buffer(piece_idx) := io.in.bits.data }
|
||||
when (pkt_done) { last := io.in.bits.last; collecting := Bool(false) }
|
||||
when (io.out.fire()) { collecting := Bool(true) }
|
||||
|
||||
io.in.ready := collecting
|
||||
io.out.valid := !collecting
|
||||
io.out.bits.data := buffer.toBits
|
||||
io.out.bits.last := last
|
||||
}
|
||||
|
||||
object StreamUtils {
|
||||
def connectStreams(a: StreamIO, b: StreamIO) {
|
||||
a.in <> b.out
|
||||
b.in <> a.out
|
||||
}
|
||||
}
|
||||
|
||||
trait Serializable {
|
||||
def nbits: Int
|
||||
}
|
||||
|
||||
class Serializer[T <: Data with Serializable](w: Int, typ: T) extends Module {
|
||||
val io = new Bundle {
|
||||
val in = Decoupled(typ).flip
|
||||
val out = Decoupled(Bits(width = w))
|
||||
}
|
||||
|
||||
val narrower = Module(new StreamNarrower(typ.nbits, w))
|
||||
narrower.io.in.bits.data := io.in.bits.toBits
|
||||
narrower.io.in.bits.last := Bool(true)
|
||||
narrower.io.in.valid := io.in.valid
|
||||
io.in.ready := narrower.io.in.ready
|
||||
io.out.valid := narrower.io.out.valid
|
||||
io.out.bits := narrower.io.out.bits.data
|
||||
narrower.io.out.ready := io.out.ready
|
||||
}
|
||||
|
||||
class Deserializer[T <: Data with Serializable](w: Int, typ: T) extends Module {
|
||||
val io = new Bundle {
|
||||
val in = Decoupled(Bits(width = w)).flip
|
||||
val out = Decoupled(typ)
|
||||
}
|
||||
|
||||
val expander = Module(new StreamExpander(w, typ.nbits))
|
||||
expander.io.in.valid := io.in.valid
|
||||
expander.io.in.bits.data := io.in.bits
|
||||
expander.io.in.bits.last := Bool(true)
|
||||
io.in.ready := expander.io.in.ready
|
||||
io.out.valid := expander.io.out.valid
|
||||
io.out.bits := typ.cloneType.fromBits(expander.io.out.bits.data)
|
||||
expander.io.out.ready := io.out.ready
|
||||
}
|
314
junctions/src/main/scala/util.scala
Normal file
314
junctions/src/main/scala/util.scala
Normal file
@ -0,0 +1,314 @@
|
||||
/// See LICENSE for license details.
|
||||
package junctions
|
||||
import Chisel._
|
||||
import cde.Parameters
|
||||
|
||||
class ParameterizedBundle(implicit p: Parameters) extends Bundle {
|
||||
override def cloneType = {
|
||||
try {
|
||||
this.getClass.getConstructors.head.newInstance(p).asInstanceOf[this.type]
|
||||
} catch {
|
||||
case e: java.lang.IllegalArgumentException =>
|
||||
throwException("Unable to use ParamaterizedBundle.cloneType on " +
|
||||
this.getClass + ", probably because " + this.getClass +
|
||||
"() takes more than one argument. Consider overriding " +
|
||||
"cloneType() on " + this.getClass, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HellaFlowQueue[T <: Data](val entries: Int)(data: => T) extends Module {
|
||||
val io = new QueueIO(data, entries)
|
||||
require(entries > 1)
|
||||
|
||||
val do_flow = Wire(Bool())
|
||||
val do_enq = io.enq.fire() && !do_flow
|
||||
val do_deq = io.deq.fire() && !do_flow
|
||||
|
||||
val maybe_full = Reg(init=Bool(false))
|
||||
val enq_ptr = Counter(do_enq, entries)._1
|
||||
val (deq_ptr, deq_done) = Counter(do_deq, entries)
|
||||
when (do_enq =/= do_deq) { maybe_full := do_enq }
|
||||
|
||||
val ptr_match = enq_ptr === deq_ptr
|
||||
val empty = ptr_match && !maybe_full
|
||||
val full = ptr_match && maybe_full
|
||||
val atLeastTwo = full || enq_ptr - deq_ptr >= UInt(2)
|
||||
do_flow := empty && io.deq.ready
|
||||
|
||||
val ram = SeqMem(entries, data)
|
||||
when (do_enq) { ram.write(enq_ptr, io.enq.bits) }
|
||||
|
||||
val ren = io.deq.ready && (atLeastTwo || !io.deq.valid && !empty)
|
||||
val raddr = Mux(io.deq.valid, Mux(deq_done, UInt(0), deq_ptr + UInt(1)), deq_ptr)
|
||||
val ram_out_valid = Reg(next = ren)
|
||||
|
||||
io.deq.valid := Mux(empty, io.enq.valid, ram_out_valid)
|
||||
io.enq.ready := !full
|
||||
io.deq.bits := Mux(empty, io.enq.bits, ram.read(raddr, ren))
|
||||
}
|
||||
|
||||
class HellaQueue[T <: Data](val entries: Int)(data: => T) extends Module {
|
||||
val io = new QueueIO(data, entries)
|
||||
|
||||
val fq = Module(new HellaFlowQueue(entries)(data))
|
||||
fq.io.enq <> io.enq
|
||||
io.deq <> Queue(fq.io.deq, 1, pipe = true)
|
||||
}
|
||||
|
||||
object HellaQueue {
|
||||
def apply[T <: Data](enq: DecoupledIO[T], entries: Int) = {
|
||||
val q = Module((new HellaQueue(entries)) { enq.bits })
|
||||
q.io.enq.valid := enq.valid // not using <> so that override is allowed
|
||||
q.io.enq.bits := enq.bits
|
||||
enq.ready := q.io.enq.ready
|
||||
q.io.deq
|
||||
}
|
||||
}
|
||||
|
||||
/** A generalized locking RR arbiter that addresses the limitations of the
|
||||
* version in the Chisel standard library */
|
||||
abstract class JunctionsAbstractLockingArbiter[T <: Data](typ: T, arbN: Int)
|
||||
extends Module {
|
||||
|
||||
val io = new Bundle {
|
||||
val in = Vec(arbN, Decoupled(typ.cloneType)).flip
|
||||
val out = Decoupled(typ.cloneType)
|
||||
}
|
||||
|
||||
def rotateLeft[T <: Data](norm: Vec[T], rot: UInt): Vec[T] = {
|
||||
val n = norm.size
|
||||
Vec.tabulate(n) { i =>
|
||||
Mux(rot < UInt(n - i), norm(UInt(i) + rot), norm(rot - UInt(n - i)))
|
||||
}
|
||||
}
|
||||
|
||||
val lockIdx = Reg(init = UInt(0, log2Up(arbN)))
|
||||
val locked = Reg(init = Bool(false))
|
||||
|
||||
val choice = PriorityMux(
|
||||
rotateLeft(Vec(io.in.map(_.valid)), lockIdx + UInt(1)),
|
||||
rotateLeft(Vec((0 until arbN).map(UInt(_))), lockIdx + UInt(1)))
|
||||
|
||||
val chosen = Mux(locked, lockIdx, choice)
|
||||
|
||||
for (i <- 0 until arbN) {
|
||||
io.in(i).ready := io.out.ready && chosen === UInt(i)
|
||||
}
|
||||
|
||||
io.out.valid := io.in(chosen).valid
|
||||
io.out.bits := io.in(chosen).bits
|
||||
}
|
||||
|
||||
/** This locking arbiter determines when it is safe to unlock
|
||||
* by peeking at the data */
|
||||
class JunctionsPeekingArbiter[T <: Data](
|
||||
typ: T, arbN: Int,
|
||||
canUnlock: T => Bool,
|
||||
needsLock: Option[T => Bool] = None)
|
||||
extends JunctionsAbstractLockingArbiter(typ, arbN) {
|
||||
|
||||
def realNeedsLock(data: T): Bool =
|
||||
needsLock.map(_(data)).getOrElse(Bool(true))
|
||||
|
||||
when (io.out.fire()) {
|
||||
when (!locked && realNeedsLock(io.out.bits)) {
|
||||
lockIdx := choice
|
||||
locked := Bool(true)
|
||||
}
|
||||
// the unlock statement takes precedent
|
||||
when (canUnlock(io.out.bits)) {
|
||||
locked := Bool(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** This arbiter determines when it is safe to unlock by counting transactions */
|
||||
class JunctionsCountingArbiter[T <: Data](
|
||||
typ: T, arbN: Int, count: Int,
|
||||
val needsLock: Option[T => Bool] = None)
|
||||
extends JunctionsAbstractLockingArbiter(typ, arbN) {
|
||||
|
||||
def realNeedsLock(data: T): Bool =
|
||||
needsLock.map(_(data)).getOrElse(Bool(true))
|
||||
|
||||
// if count is 1, you should use a non-locking arbiter
|
||||
require(count > 1, "CountingArbiter cannot have count <= 1")
|
||||
|
||||
val lock_ctr = Counter(count)
|
||||
|
||||
when (io.out.fire()) {
|
||||
when (!locked && realNeedsLock(io.out.bits)) {
|
||||
lockIdx := choice
|
||||
locked := Bool(true)
|
||||
lock_ctr.inc()
|
||||
}
|
||||
|
||||
when (locked) {
|
||||
when (lock_ctr.inc()) { locked := Bool(false) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReorderQueueWrite[T <: Data](dType: T, tagWidth: Int) extends Bundle {
|
||||
val data = dType.cloneType
|
||||
val tag = UInt(width = tagWidth)
|
||||
|
||||
override def cloneType =
|
||||
new ReorderQueueWrite(dType, tagWidth).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
class ReorderEnqueueIO[T <: Data](dType: T, tagWidth: Int)
|
||||
extends DecoupledIO(new ReorderQueueWrite(dType, tagWidth)) {
|
||||
|
||||
override def cloneType =
|
||||
new ReorderEnqueueIO(dType, tagWidth).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
class ReorderDequeueIO[T <: Data](dType: T, tagWidth: Int) extends Bundle {
|
||||
val valid = Bool(INPUT)
|
||||
val tag = UInt(INPUT, tagWidth)
|
||||
val data = dType.cloneType.asOutput
|
||||
val matches = Bool(OUTPUT)
|
||||
|
||||
override def cloneType =
|
||||
new ReorderDequeueIO(dType, tagWidth).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
class ReorderQueue[T <: Data](dType: T, tagWidth: Int, size: Option[Int] = None)
|
||||
extends Module {
|
||||
val io = new Bundle {
|
||||
val enq = new ReorderEnqueueIO(dType, tagWidth).flip
|
||||
val deq = new ReorderDequeueIO(dType, tagWidth)
|
||||
}
|
||||
|
||||
val tagSpaceSize = 1 << tagWidth
|
||||
val actualSize = size.getOrElse(tagSpaceSize)
|
||||
|
||||
if (tagSpaceSize > actualSize) {
|
||||
val roq_data = Reg(Vec(actualSize, dType))
|
||||
val roq_tags = Reg(Vec(actualSize, UInt(width = tagWidth)))
|
||||
val roq_free = Reg(init = Vec.fill(actualSize)(Bool(true)))
|
||||
|
||||
val roq_enq_addr = PriorityEncoder(roq_free)
|
||||
val roq_matches = roq_tags.zip(roq_free)
|
||||
.map { case (tag, free) => tag === io.deq.tag && !free }
|
||||
val roq_deq_onehot = PriorityEncoderOH(roq_matches)
|
||||
|
||||
io.enq.ready := roq_free.reduce(_ || _)
|
||||
io.deq.data := Mux1H(roq_deq_onehot, roq_data)
|
||||
io.deq.matches := roq_matches.reduce(_ || _)
|
||||
|
||||
when (io.enq.valid && io.enq.ready) {
|
||||
roq_data(roq_enq_addr) := io.enq.bits.data
|
||||
roq_tags(roq_enq_addr) := io.enq.bits.tag
|
||||
roq_free(roq_enq_addr) := Bool(false)
|
||||
}
|
||||
|
||||
when (io.deq.valid) {
|
||||
roq_free(OHToUInt(roq_deq_onehot)) := Bool(true)
|
||||
}
|
||||
|
||||
println(s"Warning - using a CAM for ReorderQueue, tagBits: ${tagWidth} size: ${actualSize}")
|
||||
} else {
|
||||
val roq_data = Mem(tagSpaceSize, dType)
|
||||
val roq_free = Reg(init = Vec.fill(tagSpaceSize)(Bool(true)))
|
||||
|
||||
io.enq.ready := roq_free(io.enq.bits.tag)
|
||||
io.deq.data := roq_data(io.deq.tag)
|
||||
io.deq.matches := !roq_free(io.deq.tag)
|
||||
|
||||
when (io.enq.valid && io.enq.ready) {
|
||||
roq_data(io.enq.bits.tag) := io.enq.bits.data
|
||||
roq_free(io.enq.bits.tag) := Bool(false)
|
||||
}
|
||||
|
||||
when (io.deq.valid) {
|
||||
roq_free(io.deq.tag) := Bool(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object DecoupledHelper {
|
||||
def apply(rvs: Bool*) = new DecoupledHelper(rvs)
|
||||
}
|
||||
|
||||
class DecoupledHelper(val rvs: Seq[Bool]) {
|
||||
def fire(exclude: Bool, includes: Bool*) = {
|
||||
(rvs.filter(_ ne exclude) ++ includes).reduce(_ && _)
|
||||
}
|
||||
}
|
||||
|
||||
class MultiWidthFifo(inW: Int, outW: Int, n: Int) extends Module {
|
||||
val io = new Bundle {
|
||||
val in = Decoupled(Bits(width = inW)).flip
|
||||
val out = Decoupled(Bits(width = outW))
|
||||
val count = UInt(OUTPUT, log2Up(n + 1))
|
||||
}
|
||||
|
||||
if (inW == outW) {
|
||||
val q = Module(new Queue(Bits(width = inW), n))
|
||||
q.io.enq <> io.in
|
||||
io.out <> q.io.deq
|
||||
io.count := q.io.count
|
||||
} else if (inW > outW) {
|
||||
val nBeats = inW / outW
|
||||
|
||||
require(inW % outW == 0, s"MultiWidthFifo: in: $inW not divisible by out: $outW")
|
||||
require(n % nBeats == 0, s"Cannot store $n output words when output beats is $nBeats")
|
||||
|
||||
val wdata = Reg(Vec(n / nBeats, Bits(width = inW)))
|
||||
val rdata = Vec(wdata.flatMap { indat =>
|
||||
(0 until nBeats).map(i => indat(outW * (i + 1) - 1, outW * i)) })
|
||||
|
||||
val head = Reg(init = UInt(0, log2Up(n / nBeats)))
|
||||
val tail = Reg(init = UInt(0, log2Up(n)))
|
||||
val size = Reg(init = UInt(0, log2Up(n + 1)))
|
||||
|
||||
when (io.in.fire()) {
|
||||
wdata(head) := io.in.bits
|
||||
head := head + UInt(1)
|
||||
}
|
||||
|
||||
when (io.out.fire()) { tail := tail + UInt(1) }
|
||||
|
||||
size := MuxCase(size, Seq(
|
||||
(io.in.fire() && io.out.fire()) -> (size + UInt(nBeats - 1)),
|
||||
io.in.fire() -> (size + UInt(nBeats)),
|
||||
io.out.fire() -> (size - UInt(1))))
|
||||
|
||||
io.out.valid := size > UInt(0)
|
||||
io.out.bits := rdata(tail)
|
||||
io.in.ready := size < UInt(n)
|
||||
io.count := size
|
||||
} else {
|
||||
val nBeats = outW / inW
|
||||
|
||||
require(outW % inW == 0, s"MultiWidthFifo: out: $outW not divisible by in: $inW")
|
||||
|
||||
val wdata = Reg(Vec(n * nBeats, Bits(width = inW)))
|
||||
val rdata = Vec.tabulate(n) { i =>
|
||||
Cat(wdata.slice(i * nBeats, (i + 1) * nBeats).reverse)}
|
||||
|
||||
val head = Reg(init = UInt(0, log2Up(n * nBeats)))
|
||||
val tail = Reg(init = UInt(0, log2Up(n)))
|
||||
val size = Reg(init = UInt(0, log2Up(n * nBeats + 1)))
|
||||
|
||||
when (io.in.fire()) {
|
||||
wdata(head) := io.in.bits
|
||||
head := head + UInt(1)
|
||||
}
|
||||
|
||||
when (io.out.fire()) { tail := tail + UInt(1) }
|
||||
|
||||
size := MuxCase(size, Seq(
|
||||
(io.in.fire() && io.out.fire()) -> (size - UInt(nBeats - 1)),
|
||||
io.in.fire() -> (size + UInt(1)),
|
||||
io.out.fire() -> (size - UInt(nBeats))))
|
||||
|
||||
io.count := size >> UInt(log2Up(nBeats))
|
||||
io.out.valid := io.count > UInt(0)
|
||||
io.out.bits := rdata(tail)
|
||||
io.in.ready := size < UInt(n * nBeats)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user