Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

Commit

Permalink
Coverage Passes (#689)
Browse files Browse the repository at this point in the history
* import coverage code from simulator independent converage paper
* import tests from simulator independent coverage
* fix some coverage tests
* fix CodeBase
  • Loading branch information
ekiwi authored Sep 22, 2023
1 parent ccc91cf commit 3c8d9bf
Show file tree
Hide file tree
Showing 29 changed files with 9,926 additions and 31 deletions.
184 changes: 184 additions & 0 deletions src/main/scala/chiseltest/coverage/AliasAnalysis.scala
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]])
}
150 changes: 150 additions & 0 deletions src/main/scala/chiseltest/coverage/Builder.scala
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])
}
}
Loading

0 comments on commit 3c8d9bf

Please sign in to comment.