132 lines
5.3 KiB
Scala
132 lines
5.3 KiB
Scala
// See LICENSE for license details.
|
|
|
|
package diplomacy
|
|
|
|
import Chisel._
|
|
import scala.math.{max,min}
|
|
|
|
object AddressDecoder
|
|
{
|
|
type Port = Seq[AddressSet]
|
|
type Ports = Seq[Port]
|
|
type Partition = Ports
|
|
type Partitions = Seq[Partition]
|
|
|
|
val addressOrder = Ordering.ordered[AddressSet]
|
|
val portOrder = Ordering.Iterable(addressOrder)
|
|
val partitionOrder = Ordering.Iterable(portOrder)
|
|
|
|
// Find the minimum subset of bits needed to disambiguate port addresses.
|
|
// ie: inspecting only the bits in the output, you can look at an address
|
|
// and decide to which port (outer Seq) the address belongs.
|
|
def apply(ports: Ports): BigInt = if (ports.size <= 1) 0 else {
|
|
// Every port must have at least one address!
|
|
ports.foreach { p => require (!p.isEmpty) }
|
|
// Verify the user did not give us an impossible problem
|
|
ports.combinations(2).foreach { case Seq(x, y) =>
|
|
x.foreach { a => y.foreach { b =>
|
|
require (!a.overlaps(b)) // it must be possible to disambiguate ports!
|
|
} }
|
|
}
|
|
|
|
val maxBits = log2Ceil(ports.map(_.map(_.max).max).max + 1)
|
|
val bits = (0 until maxBits).map(BigInt(1) << _).toSeq
|
|
val selected = recurse(Seq(ports.map(_.sorted).sorted(portOrder)), bits)
|
|
val output = selected.reduceLeft(_ | _)
|
|
|
|
// Modify the AddressSets to allow the new wider match functions
|
|
val widePorts = ports.map { _.map { _.widen(~output) } }
|
|
// Verify that it remains possible to disambiguate all ports
|
|
widePorts.combinations(2).foreach { case Seq(x, y) =>
|
|
x.foreach { a => y.foreach { b =>
|
|
require (!a.overlaps(b))
|
|
} }
|
|
}
|
|
|
|
output
|
|
}
|
|
|
|
// A simpler version that works for a Seq[Int]
|
|
def apply(keys: Seq[Int]): Int = {
|
|
val ports = keys.map(b => Seq(AddressSet(b, 0)))
|
|
apply(ports).toInt
|
|
}
|
|
|
|
// The algorithm has a set of partitions, discriminated by the selected bits.
|
|
// Each partion has a set of ports, listing all addresses that lead to that port.
|
|
// Seq[Seq[Seq[AddressSet]]]
|
|
// ^^^^^^^^^^^^^^^ set of addresses that are routed out this port
|
|
// ^^^ the list of ports
|
|
// ^^^ cases already distinguished by the selected bits thus far
|
|
//
|
|
// Solving this problem is NP-hard, so we use a simple greedy heuristic:
|
|
// pick the bit which minimizes the number of ports in each partition
|
|
// as a secondary goal, reduce the number of AddressSets within a partition
|
|
|
|
def bitScore(partitions: Partitions): Seq[Int] = {
|
|
val maxPortsPerPartition = partitions.map(_.size).max
|
|
val sumPortsPerPartition = partitions.map(_.size).sum
|
|
val maxSetsPerPartition = partitions.map(_.map(_.size).sum).max
|
|
val sumSetsPerPartition = partitions.map(_.map(_.size).sum).sum
|
|
Seq(maxPortsPerPartition, sumPortsPerPartition, maxSetsPerPartition, sumSetsPerPartition)
|
|
}
|
|
|
|
def partitionPort(port: Port, bit: BigInt): (Port, Port) = {
|
|
val addr_a = AddressSet(0, ~bit)
|
|
val addr_b = AddressSet(bit, ~bit)
|
|
// The addresses were sorted, so the filtered addresses are still sorted
|
|
val subset_a = port.filter(_.overlaps(addr_a))
|
|
val subset_b = port.filter(_.overlaps(addr_b))
|
|
(subset_a, subset_b)
|
|
}
|
|
|
|
def partitionPorts(ports: Ports, bit: BigInt): (Ports, Ports) = {
|
|
val partitioned_ports = ports.map(p => partitionPort(p, bit))
|
|
// because partitionPort dropped AddresSets, the ports might no longer be sorted
|
|
val case_a_ports = partitioned_ports.map(_._1).filter(!_.isEmpty).sorted(portOrder)
|
|
val case_b_ports = partitioned_ports.map(_._2).filter(!_.isEmpty).sorted(portOrder)
|
|
(case_a_ports, case_b_ports)
|
|
}
|
|
|
|
def partitionPartitions(partitions: Partitions, bit: BigInt): Partitions = {
|
|
val partitioned_partitions = partitions.map(p => partitionPorts(p, bit))
|
|
val case_a_partitions = partitioned_partitions.map(_._1).filter(!_.isEmpty)
|
|
val case_b_partitions = partitioned_partitions.map(_._2).filter(!_.isEmpty)
|
|
val new_partitions = (case_a_partitions ++ case_b_partitions).sorted(partitionOrder)
|
|
// Prevent combinational memory explosion; if two partitions are equal, keep only one
|
|
// Note: AddressSets in a port are sorted, and ports in a partition are sorted.
|
|
// This makes it easy to structurally compare two partitions for equality
|
|
val keep = (new_partitions.init zip new_partitions.tail) filter { case (a,b) => partitionOrder.compare(a,b) != 0 } map { _._2 }
|
|
new_partitions.head +: keep
|
|
}
|
|
|
|
// requirement: ports have sorted addresses and are sorted lexicographically
|
|
val debug = false
|
|
def recurse(partitions: Partitions, bits: Seq[BigInt]): Seq[BigInt] = {
|
|
if (debug) {
|
|
println("Partitioning:")
|
|
partitions.foreach { partition =>
|
|
println(" Partition:")
|
|
partition.foreach { port =>
|
|
print(" ")
|
|
port.foreach { a => print(s" ${a}") }
|
|
println("")
|
|
}
|
|
}
|
|
}
|
|
val candidates = bits.map { bit =>
|
|
val result = partitionPartitions(partitions, bit)
|
|
val score = bitScore(result)
|
|
(score, bit, result)
|
|
}
|
|
val (bestScore, bestBit, bestPartitions) = candidates.min(Ordering.by[(Seq[Int], BigInt, Partitions), Iterable[Int]](_._1.toIterable))
|
|
if (debug) println("=> Selected bit 0x%x".format(bestBit))
|
|
if (bestScore(0) <= 1) {
|
|
if (debug) println("---")
|
|
Seq(bestBit)
|
|
} else {
|
|
bestBit +: recurse(bestPartitions, bits.filter(_ != bestBit))
|
|
}
|
|
}
|
|
}
|