This repository has been archived by the owner on Aug 19, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* import coverage code from simulator independent converage paper * import tests from simulator independent coverage * fix some coverage tests * fix CodeBase
- Loading branch information
Showing
29 changed files
with
9,926 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
// Copyright 2021-2023 The Regents of the University of California | ||
// released under BSD 3-Clause License | ||
// author: Kevin Laeufer <[email protected]> | ||
|
||
package chiseltest.coverage | ||
|
||
import chiseltest.coverage.Builder.getKind | ||
import firrtl2._ | ||
import firrtl2.analyses.InstanceKeyGraph | ||
import firrtl2.analyses.InstanceKeyGraph.InstanceKey | ||
|
||
import scala.collection.mutable | ||
|
||
/** Analyses which signals in a module always have the same value (are aliases of each other). | ||
* | ||
* @note | ||
* will only work on low firrtl! | ||
* @note | ||
* right now this isn't an actual firrtl pass, but an analysis called into from a firrtl pass. | ||
*/ | ||
object AliasAnalysis { | ||
type Aliases = Seq[List[String]] | ||
type Result = Map[String, Aliases] | ||
|
||
/** @return map from module name to signals in the module that alias */ | ||
def findAliases(c: ir.Circuit, iGraph: InstanceKeyGraph): Result = { | ||
// analyze each module in isolation | ||
val local = c.modules.map(m => m.name -> findAliases(m)).toMap | ||
|
||
// compute global results | ||
val moduleOrderBottomUp = iGraph.moduleOrder.reverseIterator | ||
val childInstances = iGraph.getChildInstances.toMap | ||
val portAliases = mutable.HashMap[String, PortAliases]() | ||
|
||
val aliases = moduleOrderBottomUp.map { | ||
case m: ir.Module => | ||
val groups = resolveAliases(m, local(m.name), portAliases, childInstances(m.name)) | ||
val isPort = m.ports.map(_.name).toSet | ||
portAliases(m.name) = computePortAliases(groups, isPort) | ||
m.name -> groups | ||
case other => | ||
portAliases(other.name) = List() | ||
other.name -> List() | ||
} | ||
|
||
aliases.toMap | ||
} | ||
|
||
private type PortAliases = List[(String, String)] | ||
|
||
// Incorporate the alias information from all sub modules. | ||
// This matters if the submodule has an input and an output that aliases. | ||
private def resolveAliases( | ||
m: ir.Module, | ||
local: LocalInfo, | ||
portAliases: String => PortAliases, | ||
instances: Seq[InstanceKey] | ||
): Seq[List[String]] = { | ||
// compute any port aliases for all child modules | ||
val instancePortAliases = instances.flatMap { case InstanceKey(name, module) => | ||
portAliases(module).map { case (a, b) => | ||
(name + "." + a) -> (name + "." + b) | ||
} | ||
}.toMap | ||
|
||
// if there are no port aliases in the children, nothing is going to change | ||
if (instancePortAliases.isEmpty) return local.groups | ||
|
||
// we need to create a new group for signals that are not aliased when just looking at the local module, | ||
// but are aliased through a connection in a submodule | ||
val isAliasedPort = instancePortAliases.flatMap { case (a, b) => List(a, b) }.toSet | ||
val isGroupedSignal = local.groups.flatten.toSet | ||
val singleSignalGroups = (isAliasedPort -- isGroupedSignal).toList.sorted.map(List(_)) | ||
val localGroups = local.groups ++ singleSignalGroups | ||
|
||
// build a map from (aliasing) instance port to group id | ||
val localGroupsWithIds = localGroups.zipWithIndex | ||
val instPortToGroupId = localGroupsWithIds.flatMap { case (g, ii) => | ||
val ips = g.filter(isAliasedPort(_)) | ||
ips.map(i => i -> ii) | ||
}.toMap | ||
|
||
// check to see if there are any groups that need to be merged | ||
val merges = findMerges(instancePortAliases, instPortToGroupId) | ||
val updatedGroups = mergeGroups(localGroups, merges) | ||
|
||
updatedGroups | ||
} | ||
|
||
private def computePortAliases(groups: Seq[List[String]], isPort: String => Boolean): PortAliases = { | ||
groups.flatMap { g => | ||
val ports = g.filter(isPort) | ||
assert(ports.length < 32, s"Unexpected exponential blowup! Redesign the data-structure! $ports") | ||
ports.flatMap { a => | ||
ports.flatMap { b => | ||
if (a == b) None else Some(a -> b) | ||
} | ||
} | ||
}.toList | ||
} | ||
|
||
private def findMerges(aliases: Iterable[(String, String)], signalToGroupId: Map[String, Int]): List[Set[Int]] = { | ||
// check to see if there are any groups that need to be merged | ||
var merges = List[Set[Int]]() | ||
aliases.foreach { case (a, b) => | ||
val (aId, bId) = (signalToGroupId(a), signalToGroupId(b)) | ||
if (aId != bId) { | ||
val merge = Set(aId, bId) | ||
// update merges | ||
val bothNew = !merges.exists(s => (s & merge).nonEmpty) | ||
if (bothNew) { | ||
merges = merge +: merges | ||
} else { | ||
merges = merges.map { old => | ||
if ((old & merge).nonEmpty) { old | merge } | ||
else { old } | ||
}.distinct | ||
} | ||
} | ||
} | ||
merges | ||
} | ||
|
||
private def mergeGroups(groups: Seq[List[String]], merges: List[Set[Int]]): Seq[List[String]] = { | ||
if (merges.isEmpty) { groups } | ||
else { | ||
val merged = merges.map { m => | ||
m.toList.sorted.flatMap(i => groups(i)) | ||
} | ||
val wasMerged = merges.flatten.toSet | ||
val unmerged = groups.indices.filterNot(wasMerged).map(i => groups(i)) | ||
merged ++ unmerged | ||
} | ||
} | ||
|
||
private def findAliases(m: ir.DefModule): LocalInfo = m match { | ||
case mod: ir.Module => findAliasesM(mod) | ||
case _ => LocalInfo(List()) | ||
} | ||
|
||
private type Connects = mutable.HashMap[String, String] | ||
private def findAliasesM(m: ir.Module): LocalInfo = { | ||
// find all signals inside the module that alias | ||
val cons = new Connects() | ||
m.foreachStmt(onStmt(_, cons)) | ||
val groups = groupSignals(cons) | ||
// groups.foreach(g => println(g.mkString(" <-> "))) | ||
LocalInfo(groups) | ||
} | ||
private def groupSignals(cons: Connects): Seq[List[String]] = { | ||
val signalToGroup = mutable.HashMap[String, Int]() | ||
val groups = mutable.ArrayBuffer[List[String]]() | ||
val signals = (cons.keys.toSet | cons.values.toSet).toSeq | ||
signals.foreach { sig => | ||
signalToGroup.get(sig) match { | ||
case Some(groupId) => | ||
// we have seen this signal before, so all alias info is up to date and we just need to add it to the group! | ||
groups(groupId) = sig +: groups(groupId) | ||
case None => | ||
// check to see if any group exists under any alias name | ||
val aliases = getAliases(sig, cons) | ||
val groupId = aliases.find(a => signalToGroup.contains(a)) match { | ||
case Some(key) => signalToGroup(key) | ||
case None => groups.append(List()); groups.length - 1 | ||
} | ||
groups(groupId) = sig +: groups(groupId) | ||
aliases.foreach(a => signalToGroup(a) = groupId) | ||
} | ||
} | ||
groups.toSeq | ||
} | ||
private def getAliases(name: String, cons: Connects): List[String] = cons.get(name) match { | ||
case None => List(name) | ||
case Some(a) => name +: getAliases(a, cons) | ||
} | ||
private def onStmt(s: ir.Statement, cons: Connects): Unit = s match { | ||
case ir.DefNode(_, lhs, rhs: ir.RefLikeExpression) => | ||
cons(lhs) = rhs.serialize | ||
case ir.Connect(_, lhs: ir.RefLikeExpression, rhs: ir.RefLikeExpression) if getKind(lhs) != RegKind => | ||
cons(lhs.serialize) = rhs.serialize | ||
case other => other.foreachStmt(onStmt(_, cons)) | ||
} | ||
private case class LocalInfo(groups: Seq[List[String]]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
// Copyright 2021-2023 The Regents of the University of California | ||
// released under BSD 3-Clause License | ||
// author: Kevin Laeufer <[email protected]> | ||
|
||
package chiseltest.coverage | ||
|
||
import firrtl2._ | ||
import firrtl2.annotations.{IsModule, ReferenceTarget} | ||
import firrtl2.logger.Logger | ||
|
||
import scala.collection.mutable | ||
|
||
/** Helps us construct well typed low-ish firrtl. Some of these convenience functions could be moved to firrtl at some | ||
* point. | ||
*/ | ||
object Builder { | ||
|
||
/** Fails if there isn't exactly one Clock input */ | ||
def findClock(m: ir.Module): ir.RefLikeExpression = { | ||
val clocks = findClocks(m) | ||
assert( | ||
clocks.length == 1, | ||
s"[${m.name}] This transformation only works if there is exactly one clock.\n" + | ||
s"Found: ${clocks.map(_.serialize)}\n" | ||
) | ||
clocks.head | ||
} | ||
|
||
def findClock(mod: ir.Module, logger: Logger): Option[ir.RefLikeExpression] = { | ||
val clocks = Builder.findClocks(mod) | ||
if (clocks.isEmpty) { | ||
logger.warn(s"WARN: [${mod.name}] found no clock input, skipping ...") | ||
} | ||
if (clocks.length > 1) { | ||
logger.warn( | ||
s"WARN: [${mod.name}] found more than one clock, picking the first one: " + clocks | ||
.map(_.serialize) | ||
.mkString(", ") | ||
) | ||
} | ||
clocks.headOption | ||
} | ||
|
||
def findClocks(m: ir.Module): Seq[ir.RefLikeExpression] = { | ||
val ports = flattenedPorts(m.ports) | ||
val clockIO = ports.filter(_.tpe == ir.ClockType) | ||
val clockInputs = clockIO.filter(_.flow == SourceFlow) | ||
|
||
val isAsyncQueue = m.name == "AsyncQueue" || m.name.startsWith("AsyncQueue_") | ||
if (isAsyncQueue) { | ||
// The "clock" input of the AsyncQueue from rocketchip is unused | ||
// thus, even if both sides of the AsyncQueue are in the same clock domain (which is an assumption that we make) | ||
// using "clock" will lead to counters that never increment. | ||
// Using any of the other clocks is fine! | ||
clockInputs.filterNot(_.serialize == "clock") | ||
} else { | ||
clockInputs | ||
} | ||
} | ||
|
||
def refToTarget(module: IsModule, ref: ir.RefLikeExpression): ReferenceTarget = ref match { | ||
case ir.Reference(name, _, _, _) => module.ref(name) | ||
case ir.SubField(expr, name, _, _) => refToTarget(module, expr.asInstanceOf[ir.RefLikeExpression]).field(name) | ||
case ir.SubIndex(expr, value, _, _) => refToTarget(module, expr.asInstanceOf[ir.RefLikeExpression]).index(value) | ||
case other => throw new RuntimeException(s"Unsupported reference expression: $other") | ||
} | ||
|
||
private def flattenedPorts(ports: Seq[ir.Port]): Seq[ir.RefLikeExpression] = { | ||
ports.flatMap { p => expandRef(ir.Reference(p.name, p.tpe, PortKind, Utils.to_flow(p.direction))) } | ||
} | ||
|
||
private def expandRef(ref: ir.RefLikeExpression): Seq[ir.RefLikeExpression] = ref.tpe match { | ||
case ir.BundleType(fields) => | ||
Seq(ref) ++ fields.flatMap(f => expandRef(ir.SubField(ref, f.name, f.tpe, Utils.times(f.flip, ref.flow)))) | ||
case _ => Seq(ref) | ||
} | ||
|
||
def findResets(m: ir.Module): Seq[ir.RefLikeExpression] = { | ||
val ports = flattenedPorts(m.ports) | ||
val inputs = ports.filter(_.flow == SourceFlow) | ||
val ofResetType = inputs.filter(p => p.tpe == ir.AsyncResetType || p.tpe == ir.ResetType) | ||
val boolWithCorrectName = inputs.filter(p => p.tpe == ir.UIntType(ir.IntWidth(1)) && p.serialize.endsWith("reset")) | ||
val resetInputs = ofResetType ++ boolWithCorrectName | ||
resetInputs | ||
} | ||
|
||
def reduceAnd(e: ir.Expression): ir.Expression = ir.DoPrim(PrimOps.Andr, List(e), List(), Utils.BoolType) | ||
|
||
def add(a: ir.Expression, b: ir.Expression): ir.Expression = { | ||
val (aWidth, bWidth) = (getWidth(a.tpe), getWidth(b.tpe)) | ||
val resultWidth = Seq(aWidth, bWidth).max | ||
val (aPad, bPad) = (pad(a, resultWidth), pad(b, resultWidth)) | ||
val res = ir.DoPrim(PrimOps.Add, List(aPad, bPad), List(), withWidth(a.tpe, resultWidth + 1)) | ||
ir.DoPrim(PrimOps.Bits, List(res), List(resultWidth - 1, 0), withWidth(a.tpe, resultWidth)) | ||
} | ||
|
||
def pad(e: ir.Expression, to: BigInt): ir.Expression = { | ||
val from = getWidth(e.tpe) | ||
require(to >= from) | ||
if (to == from) { e } | ||
else { ir.DoPrim(PrimOps.Pad, List(e), List(to), withWidth(e.tpe, to)) } | ||
} | ||
|
||
def withWidth(tpe: ir.Type, width: BigInt): ir.Type = tpe match { | ||
case ir.UIntType(_) => ir.UIntType(ir.IntWidth(width)) | ||
case ir.SIntType(_) => ir.SIntType(ir.IntWidth(width)) | ||
case other => throw new RuntimeException(s"Cannot change the width of $other!") | ||
} | ||
|
||
def getWidth(tpe: ir.Type): BigInt = firrtl2.bitWidth(tpe) | ||
|
||
def makeRegister( | ||
stmts: mutable.ListBuffer[ir.Statement], | ||
info: ir.Info, | ||
name: String, | ||
tpe: ir.Type, | ||
clock: ir.Expression, | ||
next: ir.Expression, | ||
reset: ir.Expression = Utils.False(), | ||
init: Option[ir.Expression] = None | ||
): ir.Reference = { | ||
if (isAsyncReset(reset)) { | ||
val initExpr = init.getOrElse(ir.Reference(name, tpe, RegKind)) | ||
val reg = ir.DefRegister(info, name, tpe, clock, reset, initExpr) | ||
stmts.append(reg) | ||
stmts.append(ir.Connect(info, ir.Reference(reg), next)) | ||
ir.Reference(reg) | ||
} else { | ||
val ref = ir.Reference(name, tpe, RegKind, UnknownFlow) | ||
stmts.append(ir.DefRegister(info, name, tpe, clock, Utils.False(), ref)) | ||
init match { | ||
case Some(value) => stmts.append(ir.Connect(info, ref, Utils.mux(reset, value, next))) | ||
case None => stmts.append(ir.Connect(info, ref, next)) | ||
} | ||
ref | ||
} | ||
} | ||
|
||
def isAsyncReset(reset: ir.Expression): Boolean = reset.tpe match { | ||
case ir.AsyncResetType => true | ||
case _ => false | ||
} | ||
|
||
def getKind(ref: ir.RefLikeExpression): firrtl2.Kind = ref match { | ||
case ir.Reference(_, _, kind, _) => kind | ||
case ir.SubField(expr, _, _, _) => getKind(expr.asInstanceOf[ir.RefLikeExpression]) | ||
case ir.SubIndex(expr, _, _, _) => getKind(expr.asInstanceOf[ir.RefLikeExpression]) | ||
case ir.SubAccess(expr, _, _, _) => getKind(expr.asInstanceOf[ir.RefLikeExpression]) | ||
} | ||
} |
Oops, something went wrong.