@@ -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
10601128def getSet (using MemberGetSet ): MemberGetSet = summon[MemberGetSet ]
0 commit comments