Skip to content

Commit 95f53de

Browse files
authored
Merge pull request #284 from DFiantHDL/constraints
v0.14.0
2 parents b037bbb + 8674589 commit 95f53de

File tree

214 files changed

+4059
-1476
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

214 files changed

+4059
-1476
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ jobs:
2727
steps:
2828
- name: Checkout
2929
uses: actions/checkout@v3
30+
with:
31+
submodules: true
32+
fetch-depth: 0
3033
- name: Setup Scala
3134
uses: coursier/setup-action@v1
3235
with:

.github/workflows/release.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ jobs:
1212
publish:
1313
runs-on: ubuntu-latest
1414
steps:
15-
- uses: actions/checkout@v3
15+
- name: Checkout
16+
uses: actions/checkout@v3
1617
with:
18+
submodules: true
1719
fetch-depth: 0
1820
- uses: coursier/setup-action@v1
1921
with:

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "platforms"]
2+
path = platforms
3+
url = https://github.com/DFiantHDL/dfhdl-platforms

.scalafmt.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version = 3.9.8
1+
version = 3.9.9
22
runner.dialect = scala3
33

44
maxColumn = 100

build.sbt

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ commands += DFHDLCommands.docExamplesRefUpdate
55

66
// format: off
77
val projectName = "dfhdl"
8-
val compilerVersion = "3.7.1"
8+
val compilerVersion = "3.7.2"
99

1010
inThisBuild(
1111
List(
@@ -48,7 +48,7 @@ lazy val root = (project in file("."))
4848
core,
4949
compiler_stages,
5050
lib,
51-
devices
51+
platforms
5252
)
5353

5454
lazy val internals = project
@@ -119,18 +119,14 @@ lazy val lib = project
119119
compiler_stages
120120
)
121121

122-
lazy val devices = (project in file("devices"))
122+
lazy val platforms = project
123123
.settings(
124-
name := s"$projectName-devices",
124+
name := s"$projectName-platforms",
125125
settings,
126126
pluginUseSettings,
127127
libraryDependencies ++= commonDependencies
128128
)
129129
.dependsOn(
130-
plugin,
131-
internals,
132-
compiler_ir,
133-
core,
134130
lib
135131
)
136132

@@ -140,7 +136,7 @@ lazy val dependencies =
140136
new {
141137
private val scodecV = "1.2.4"
142138
private val munitV = "1.1.1"
143-
private val airframelogV = "2025.1.14"
139+
private val airframelogV = "2025.1.16"
144140
private val oslibV = "0.9.2"
145141
private val scallopV = "5.2.0"
146142
private val upickleV = "4.2.1"

compiler/ir/src/main/scala/dfhdl/compiler/ir/Config.scala

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ object ConfigN:
1010
given [T1, T2](using CanEqual[T1, T2]): CanEqual[ConfigN[T1], ConfigN[T2]] = CanEqual.derived
1111
given [T]: CanEqual[ConfigN[T], None.type] = CanEqual.derived
1212
given [T]: CanEqual[None.type, ConfigN[T]] = CanEqual.derived
13+
given [T]: CanEqual[ConfigN[T], T] = CanEqual.derived
14+
given [T]: CanEqual[T, ConfigN[T]] = CanEqual.derived
1315
given [L, R]: CanEqual[ConfigN[L], ConfigN[R]] = CanEqual.derived
1416
given [T](using ReadWriter[T]): ReadWriter[ConfigN[T]] = readwriter[ujson.Value].bimap(
1517
value =>
@@ -22,6 +24,28 @@ object ConfigN:
2224
case ujson.Null => None
2325
case value => read[T](value)
2426
)
27+
extension [T](x: ConfigN[T])
28+
def getOrElse(default: => T): T = x match
29+
case None => default
30+
case value: T @unchecked => value
31+
def foreach(f: T => Unit): Unit = x match
32+
case None => ()
33+
case value: T @unchecked => f(value)
34+
def map[R](f: T => R): ConfigN[R] = x match
35+
case None => None
36+
case value: T @unchecked => f(value)
37+
def flatMap[R](f: T => ConfigN[R]): ConfigN[R] = x match
38+
case None => None
39+
case value: T @unchecked => f(value)
40+
def toList: List[T] = x match
41+
case None => Nil
42+
case value: T @unchecked => List(value)
43+
end extension
44+
extension [T <: DFRefAny](x: ConfigN[T])
45+
def =~(that: ConfigN[T])(using MemberGetSet): Boolean = (x, that) match
46+
case (None, None) => true
47+
case (t: T @unchecked, that: T @unchecked) => t =~ that
48+
case _ => false
2549
end ConfigN
2650

2751
/** Sets the policy for inclusing the clock or reset signals when they are not needed
@@ -40,13 +64,9 @@ object ClkCfg:
4064
enum Edge extends StableEnum derives CanEqual, ReadWriter:
4165
case Rising, Falling
4266

43-
type RateData = (BigDecimal, DFFreq.Unit | DFTime.Unit)
44-
given ReadWriter[DFFreq.Unit | DFTime.Unit] =
45-
ReadWriter.merge(summon[ReadWriter[DFTime.Unit]], summon[ReadWriter[DFFreq.Unit]])
46-
4767
final case class Explicit(
4868
edge: Edge,
49-
rate: RateData,
69+
rate: RateNumber,
5070
portName: String,
5171
inclusionPolicy: ClkRstInclusionPolicy
5272
) derives CanEqual,

compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ final case class DB(
3939
case _ => true
4040
}
4141

42+
// considered to be in build if not in simulation and has a device constraint
43+
lazy val inBuild: Boolean = !inSimulation && top.dclMeta.annotations.exists {
44+
case _: constraints.DeviceID => true
45+
case _ => false
46+
}
47+
4248
lazy val portsByName: Map[DFDesignInst, Map[String, DFVal.Dcl]] =
4349
members.view
4450
.collect { case m: DFVal.Dcl if m.isPort => m }
@@ -47,6 +53,10 @@ final case class DB(
4753
design -> dcls.map(m => m.getRelativeName(design) -> m).toMap
4854
}.toMap
4955

56+
lazy val topIOs: List[DFVal.Dcl] = designMemberTable(top).collect {
57+
case dcl: DFVal.Dcl if dcl.isPort => dcl
58+
}
59+
5060
// map of all ports and their by-name selectors
5161
lazy val portsByNameSelectors: Map[DFVal.Dcl, List[DFVal.PortByNameSelect]] =
5262
members.view
@@ -81,7 +91,7 @@ final case class DB(
8191
origMember.getRefs.foreach {
8292
case _: DFRef.Empty =>
8393
case _: DFRef.TypeRef if excludeTypeRef =>
84-
case r =>
94+
case r =>
8595
tbl.updateWith(
8696
refTable.getOrElse(
8797
r,
@@ -275,7 +285,7 @@ final case class DB(
275285
): (DFConditional.Header, List[DFConditional.Block]) =
276286
handled += block
277287
block.prevBlockOrHeaderRef.get match
278-
case header: DFConditional.Header => (header, block :: chain)
288+
case header: DFConditional.Header => (header, block :: chain)
279289
case prevBlock: DFConditional.Block =>
280290
getChain(prevBlock, block :: chain)
281291
chainMap + getChain(m, Nil)
@@ -391,14 +401,14 @@ final case class DB(
391401
val toValOption = (lhsAccess, rhsAccess) match
392402
case (Write, Read | ReadWrite | Unknown) => Some(lhsVal)
393403
case (Read | ReadWrite | Unknown, Write) => Some(rhsVal)
394-
case (Read, Read) =>
404+
case (Read, Read) =>
395405
newError("Unsupported read-to-read connection.")
396406
None
397407
case (Write, Write) =>
398408
newError("Unsupported write-to-write connection.")
399409
None
400-
case (_, Read) => Some(lhsVal)
401-
case (Read, _) => Some(rhsVal)
410+
case (_, Read) => Some(lhsVal)
411+
case (Read, _) => Some(rhsVal)
402412
case (Error, _) =>
403413
newError(s"Unknown access pattern with ${lhsVal.relValString}.")
404414
None
@@ -834,25 +844,24 @@ final case class DB(
834844
|Message: $msg""".stripMargin
835845
val ownerDomain = wait.getOwnerDomain
836846
trigger.getConstData match
837-
case Some((waitValue: BigDecimal, waitUnit: DFTime.Unit)) =>
847+
case Some(waitTime: TimeNumber) =>
838848
// Check if the wait statement is in a domain with a clock rate configuration
839849
explicitRTDomainCfgMap.get(ownerDomain) match
840850
case Some(RTDomainCfg.Explicit(_, clkCfg: ClkCfg.Explicit, _)) =>
851+
841852
// Get the clock period in picoseconds
842-
val (clockPeriodPs: BigDecimal, desc: String) = clkCfg.rate match
843-
case (value: BigDecimal, unit: DFTime.Unit) =>
844-
// Direct period specification
845-
(unit.to_ps(value), s"period ${value}.${unit}")
846-
case (value: BigDecimal, unit: DFFreq.Unit) =>
847-
// Frequency specification - convert to period
848-
(unit.to_ps(value), s"frequency ${value}.${unit}")
853+
val clockPeriodPs = clkCfg.rate.to_ps.value
854+
val desc = clkCfg.rate match
855+
case time: TimeNumber => s"period ${time}"
856+
case freq: FreqNumber => s"frequency ${freq}"
857+
849858
// Get wait duration in picoseconds
850-
val waitDurationPs = waitUnit.to_ps(waitValue)
859+
val waitDurationPs = waitTime.to_ps.value
851860

852861
// Check if wait duration is exactly divisible by clock period
853862
if (waitDurationPs % clockPeriodPs != 0)
854863
waitError(
855-
s"Wait duration ${waitValue}.${waitUnit} is not exactly divisible by the clock $desc."
864+
s"Wait duration ${waitTime} is not exactly divisible by the clock $desc."
856865
)
857866
case _ =>
858867
waitError(
@@ -941,7 +950,7 @@ final case class DB(
941950
val problemReferences: List[(DFMember, DFMember)] =
942951
membersNoGlobals.view.drop(1).flatMap {
943952
case _: PortByNameSelect => None
944-
case m =>
953+
case m =>
945954
val isDesignParam = m match
946955
case _: DFVal.DesignParam => true
947956
case _ => false
@@ -995,6 +1004,62 @@ final case class DB(
9951004
)
9961005
end directRefCheck
9971006

1007+
def portLocationCheck(): Unit =
1008+
val errors = mutable.ListBuffer.empty[String]
1009+
val locationCollisions = mutable.ListBuffer.empty[String]
1010+
1011+
// Collect all location constraints to check for collisions
1012+
val locationMap = mutable.Map.empty[String, String] // loc -> portName(idx)
1013+
1014+
topIOs.foreach(port =>
1015+
val bitSet = mutable.BitSet((0 until port.width)*)
1016+
port.meta.annotations.foreach {
1017+
case constraints.IO(bitIdx = None, loc = loc: String) =>
1018+
bitSet.clear()
1019+
locationMap.get(loc).foreach { prevPort =>
1020+
locationCollisions += s"${prevPort} and ${port.getFullName} are both assigned to location `${loc}`"
1021+
}
1022+
locationMap += loc -> port.getFullName
1023+
if (port.width != 1)
1024+
locationCollisions += s"${port.getFullName} has mutliple bits assigned to location `${loc}`"
1025+
case constraints.IO(bitIdx = bitIdx: Int, loc = loc: String) =>
1026+
locationMap.get(loc).foreach { prevPort =>
1027+
locationCollisions += s"${prevPort} and ${port.getFullName}(${bitIdx}) are both assigned to location `${loc}`"
1028+
}
1029+
locationMap += loc -> s"${port.getFullName}(${bitIdx})"
1030+
bitSet -= bitIdx
1031+
case _ =>
1032+
}
1033+
if (bitSet.nonEmpty)
1034+
if (port.width == 1)
1035+
errors += s"${port.getFullName}"
1036+
else
1037+
errors += s"${port.getFullName} with bits ${bitSet.mkString(", ")}"
1038+
)
1039+
1040+
if (errors.nonEmpty)
1041+
throw new IllegalArgumentException(
1042+
s"""|The following top ports are missing location constraints:
1043+
| ${errors.mkString("\n ")}
1044+
|To Fix:
1045+
|Add a location constraint to the ports by connecting them to a located resource or
1046+
|by using the `@io` constraint.
1047+
|""".stripMargin
1048+
)
1049+
1050+
if (locationCollisions.nonEmpty)
1051+
throw new IllegalArgumentException(
1052+
s"""|The following location constraints have collisions:
1053+
| ${locationCollisions.mkString("\n ")}
1054+
|To Fix:
1055+
|Ensure each location is used by a single port bit.
1056+
|""".stripMargin
1057+
)
1058+
end portLocationCheck
1059+
1060+
def buildTopChecks(): Unit =
1061+
portLocationCheck()
1062+
9981063
def check(): Unit =
9991064
nameCheck()
10001065
connectionTable // causes connectivity checks
@@ -1003,6 +1068,8 @@ final case class DB(
10031068
directRefCheck()
10041069
circularDerivedDomainsCheck()
10051070
waitCheck()
1071+
if (inBuild)
1072+
buildTopChecks()
10061073

10071074
// There can only be a single connection to a value in a given range
10081075
// (multiple assignments are possible)
@@ -1056,5 +1123,6 @@ trait MemberGetSet:
10561123
def remove[M <: DFMember](member: M): M
10571124
def setGlobalTag[CT <: DFTag: ClassTag](tag: CT): Unit
10581125
def getGlobalTag[CT <: DFTag: ClassTag]: Option[CT]
1126+
final lazy val topName: String = designDB.top.dclName
10591127

10601128
def getSet(using MemberGetSet): MemberGetSet = summon[MemberGetSet]

0 commit comments

Comments
 (0)