Skip to content

Commit de2b576

Browse files
author
Oron Port
committed
basic vivado constraints and build is working
1 parent 1891d2e commit de2b576

File tree

4 files changed

+85
-38
lines changed

4 files changed

+85
-38
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ object PhysicalNumber:
4646
case time: TimeNumber => TimeNumber.to_ps(time)
4747
case freq: FreqNumber => FreqNumber.to_ps(freq)
4848
end to_ps
49+
def to_ns: TimeNumber = TimeNumber(lhs.to_ps.value / BigDecimal(1000), TimeNumber.Unit.ns)
4950
def to_period: TimeNumber =
5051
lhs match
5152
case time: TimeNumber => time

compiler/ir/src/main/scala/dfhdl/hw/annotation.scala

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import scala.annotation.Annotation
99
import upickle.default.*
1010
import dfhdl.internals.StableEnum
1111
import dfhdl.compiler.ir.ConfigN
12+
import dfhdl.compiler.ir.{RateNumber, FreqNumber, TimeNumber}
1213

1314
object annotation:
1415
sealed abstract class HWAnnotation extends StaticAnnotation, Product, Serializable, HasCodeString
@@ -74,23 +75,28 @@ object annotation:
7475
end annotation
7576

7677
object constraints:
77-
sealed abstract class Constraint extends annotation.HWAnnotation:
78+
sealed abstract class Constraint extends annotation.HWAnnotation derives ReadWriter:
7879
val isActive: Boolean = true
79-
sealed abstract class SigConstraint extends Constraint:
80+
sealed abstract class SigConstraint extends Constraint derives ReadWriter:
8081
val bitIdx: ConfigN[Int]
81-
object Constraint:
82-
given ReadWriter[Constraint] = ReadWriter.merge(
83-
summon[ReadWriter[io]],
84-
summon[ReadWriter[device]]
85-
)
86-
87-
final case class device(name: String, properties: (String, String)*)
82+
final case class device(name: String, properties: Map[String, String])
8883
extends Constraint
8984
derives CanEqual, ReadWriter:
85+
def this(name: String, properties: (String, String)*) =
86+
this(name, properties.toMap)
9087
def codeString(using Printer): String =
9188
val props = properties.map { case (k, v) => s""""$k" -> "$v"""" }.mkString(", ")
9289
s"""@device("$name"${props.emptyOr(", " + _)})"""
9390

91+
private def csParam[T](name: String, value: ConfigN[T])(using printer: Printer): String =
92+
value match
93+
case None => ""
94+
case cs: HasCodeString => s"$name = ${cs.codeString}"
95+
case str: String => s"$name = \"$str\""
96+
case num: FreqNumber => s"$name = ${printer.csDFFreqData(num)}"
97+
case num: TimeNumber => s"$name = ${printer.csDFTimeData(num)}"
98+
case _ => s"$name = ${value}"
99+
94100
final case class io(
95101
bitIdx: ConfigN[Int] = None,
96102
loc: ConfigN[String] = None,
@@ -100,19 +106,13 @@ object constraints:
100106
pullMode: ConfigN[io.PullMode] = None
101107
) extends SigConstraint derives CanEqual, ReadWriter:
102108
def codeString(using Printer): String =
103-
def byName[T](name: String, value: ConfigN[T]): String =
104-
value match
105-
case None => ""
106-
case cs: HasCodeString => s"$name = ${cs.codeString}"
107-
case str: String => s"$name = \"$str\""
108-
case _ => s"$name = ${value}"
109109
val params = List(
110-
byName("bitIdx", bitIdx),
111-
byName("loc", loc),
112-
byName("standard", standard),
113-
byName("slewRate", slewRate),
114-
byName("driveStrength", driveStrength),
115-
byName("pullMode", pullMode)
110+
csParam("bitIdx", bitIdx),
111+
csParam("loc", loc),
112+
csParam("standard", standard),
113+
csParam("slewRate", slewRate),
114+
csParam("driveStrength", driveStrength),
115+
csParam("pullMode", pullMode)
116116
).filter(_.nonEmpty).mkString(", ")
117117
s"""@io($params)"""
118118
end codeString
@@ -129,12 +129,22 @@ object constraints:
129129
def codeString(using Printer): String = "io.PullMode." + this.toString
130130

131131
object timing:
132-
final case class ignore(bitIdx: ConfigN[Int] = None)
133-
extends SigConstraint
132+
final case class ignore(
133+
bitIdx: ConfigN[Int] = None,
134+
maxFreqMinPeriod: ConfigN[RateNumber] = None
135+
) extends SigConstraint
134136
derives CanEqual, ReadWriter:
135137
def codeString(using Printer): String =
136-
val params = bitIdx match
137-
case None => ""
138-
case bitIdx => s"bitIdx = $bitIdx"
138+
val params = List(
139+
csParam("bitIdx", bitIdx),
140+
csParam("maxFreqMinPeriod", maxFreqMinPeriod)
141+
).filter(_.nonEmpty).mkString(", ")
139142
s"""@timing.ignore($params)"""
143+
144+
final case class clock(
145+
rate: RateNumber
146+
) extends Constraint derives CanEqual, ReadWriter:
147+
def codeString(using Printer): String =
148+
s"""@timing.clock(${csParam("rate", rate)})"""
149+
end timing
140150
end constraints

core/src/main/scala/dfhdl/core/DFVal.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,12 @@ sealed protected trait DFValLP:
295295
x =>
296296
import dfc.getSet
297297
x.asIR.getConstData.get.asInstanceOf[ir.RateNumber]
298+
given DFRateToRateNumberConfigN(using
299+
dfc: DFC
300+
): Conversion[DFConstOf[DFTime | DFFreq], ir.ConfigN[ir.RateNumber]] =
301+
x =>
302+
import dfc.getSet
303+
x.asIR.getConstData.get.asInstanceOf[ir.ConfigN[ir.RateNumber]]
298304
end DFValLP
299305
object DFVal extends DFValLP:
300306
protected type FieldWithModifier[V, M <: ModifierAny] = V match

lib/src/main/scala/dfhdl/tools/toolsCore/Vivado.scala

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import dfhdl.backends
1212
import dfhdl.compiler.stages.verilog.VerilogDialect
1313
import dfhdl.compiler.stages.vhdl.VHDLDialect
1414
import dfhdl.hw.constraints
15+
import dfhdl.compiler.ir.RateNumber
1516

1617
object Vivado extends Builder:
1718
val toolName: String = "Vivado"
@@ -38,7 +39,7 @@ object Vivado extends Builder:
3839
)(using CompilerOptions, BuilderOptions): CompiledDesign =
3940
given MemberGetSet = cd.stagedDB.getSet
4041
exec(
41-
s"-mode batch -source ${topName}.tcl"
42+
s"-mode batch -script ${topName}.tcl"
4243
)
4344
cd
4445
end Vivado
@@ -118,12 +119,15 @@ class VivadoProjectConstraintsPrinter(using getSet: MemberGetSet, co: CompilerOp
118119
case _ => Nil
119120
}.toList
120121

121-
def portPattern(port: DFVal.Dcl, constraint: constraints.SigConstraint): String =
122+
def xdc_get_ports(port: DFVal.Dcl, constraint: constraints.SigConstraint): String =
122123
val portName = port.getName
123-
constraint.bitIdx match
124-
case None => s"$portName[*]"
125-
case bitIdx => s"$portName[$bitIdx]"
126-
end portPattern
124+
val portPattern =
125+
port.dfType match
126+
case DFBit | DFBool => portName
127+
case _ => constraint.bitIdx match
128+
case None => s"$portName[*]"
129+
case bitIdx => s"$portName[$bitIdx]"
130+
s"[get_ports {$portPattern}]"
127131

128132
def xdcIOConstraint(
129133
port: DFVal.Dcl,
@@ -170,28 +174,54 @@ class VivadoProjectConstraintsPrinter(using getSet: MemberGetSet, co: CompilerOp
170174
addToDict("PULLMODE", pullModeStr)
171175
}
172176

173-
s"set_property -dict {$dict} [get_ports {${portPattern(port, portConstraint)}}]"
177+
s"set_property -dict {$dict} ${xdc_get_ports(port, portConstraint)}"
174178
end xdcIOConstraint
175179

180+
// Vivado has a hard limit of ~200us for the clock period, even for virtual clocks
181+
val VivadoMaxClockPeriodNS = BigDecimal(200000)
182+
176183
def xdcTimingIgnoreConstraint(
177184
port: DFVal.Dcl,
178185
constraint: constraints.timing.ignore
179186
): String =
180-
def helper(dir: String): String =
181-
s"set_false_path $dir [get_ports {${portPattern(port, constraint)}}]"
187+
def set_false_path(dir: String): String =
188+
s"set_false_path $dir ${xdc_get_ports(port, constraint)}"
189+
def set_io_delay(dir: String): String =
190+
constraint.maxFreqMinPeriod match
191+
case None => ""
192+
case maxFreqMinPeriod: RateNumber =>
193+
val maxFreqMinPeriodNS = maxFreqMinPeriod.to_ns.value.min(VivadoMaxClockPeriodNS)
194+
val virtualClockName = s"virtual_clock_${port.getName}"
195+
//format: off
196+
s"""|
197+
|create_clock -period $maxFreqMinPeriodNS -name $virtualClockName
198+
|set_${dir}_delay -clock [get_clocks $virtualClockName] -min 0.0 ${xdc_get_ports(port,constraint)}
199+
|set_${dir}_delay -clock [get_clocks $virtualClockName] -max 0.0 ${xdc_get_ports(port,constraint)}""".stripMargin
200+
//format: on
201+
case _ => ""
182202
(port.modifier.dir: @unchecked) match
183-
case DFVal.Modifier.IN => helper("-from")
184-
case DFVal.Modifier.OUT => helper("-to")
203+
case DFVal.Modifier.IN =>
204+
set_false_path("-from") + set_io_delay("input")
205+
case DFVal.Modifier.OUT =>
206+
set_false_path("-to") + set_io_delay("output")
185207
// TODO: for INOUT, also check that its actually used in both directions by the design
186-
case DFVal.Modifier.INOUT => helper("-from") + "\n" + helper("-to")
208+
case DFVal.Modifier.INOUT => set_false_path("-from") + set_false_path("-to")
187209
end xdcTimingIgnoreConstraint
188210

211+
def xdcTimingClockConstraint(
212+
port: DFVal.Dcl,
213+
constraint: constraints.timing.clock
214+
): String =
215+
s"create_clock -add -name ${port.getName} -period ${constraint.rate.to_ns.value.bigDecimal.toPlainString} [get_ports {${port.getName}}]"
216+
end xdcTimingClockConstraint
217+
189218
def xdcPortConstraints(
190219
port: DFVal.Dcl
191220
): List[String] =
192221
port.meta.annotations.collect {
193222
case constraint: constraints.io => xdcIOConstraint(port, constraint)
194223
case constraint: constraints.timing.ignore => xdcTimingIgnoreConstraint(port, constraint)
224+
case constraint: constraints.timing.clock => xdcTimingClockConstraint(port, constraint)
195225
}
196226
end xdcPortConstraints
197227

0 commit comments

Comments
 (0)