Skip to content

Commit e216bb8

Browse files
author
Oron Port
committed
timing constraints and vivado build wip
1 parent 95cb033 commit e216bb8

File tree

3 files changed

+179
-35
lines changed

3 files changed

+179
-35
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ object ConfigN:
2222
case ujson.Null => None
2323
case value => read[T](value)
2424
)
25+
extension [T](x: ConfigN[T])
26+
def getOrElse(default: => T): T = x match
27+
case None => default
28+
case value: T @unchecked => value
29+
def foreach(f: T => Unit): Unit = x match
30+
case None => ()
31+
case value: T @unchecked => f(value)
32+
def map[R](f: T => R): ConfigN[R] = x match
33+
case None => None
34+
case value: T @unchecked => f(value)
35+
def flatMap[R](f: T => ConfigN[R]): ConfigN[R] = x match
36+
case None => None
37+
case value: T @unchecked => f(value)
2538
end ConfigN
2639

2740
/** Sets the policy for inclusing the clock or reset signals when they are not needed

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

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package dfhdl.hw
44

55
import dfhdl.compiler.printing.{Printer, HasCodeString}
66
import scala.annotation.StaticAnnotation
7-
import dfhdl.internals.HasTypeName
7+
import dfhdl.internals.*
88
import scala.annotation.Annotation
99
import upickle.default.*
1010
import dfhdl.internals.StableEnum
@@ -76,48 +76,65 @@ end annotation
7676
object constraints:
7777
sealed abstract class Constraint extends annotation.HWAnnotation:
7878
val isActive: Boolean = true
79+
sealed abstract class SigConstraint extends Constraint:
80+
val bitIdx: ConfigN[Int]
7981
object Constraint:
8082
given ReadWriter[Constraint] = ReadWriter.merge(
81-
summon[ReadWriter[io.IOConstraints]]
83+
summon[ReadWriter[io]],
84+
summon[ReadWriter[device]]
8285
)
8386

87+
final case class device(name: String, properties: (String, String)*)
88+
extends Constraint
89+
derives CanEqual, ReadWriter:
90+
def codeString(using Printer): String =
91+
val props = properties.map { case (k, v) => s""""$k" -> "$v"""" }.mkString(", ")
92+
s"""@device("$name"${props.emptyOr(", " + _)})"""
93+
94+
final case class io(
95+
bitIdx: ConfigN[Int] = None,
96+
loc: ConfigN[String] = None,
97+
standard: ConfigN[io.Standard] = None,
98+
slewRate: ConfigN[io.SlewRate] = None,
99+
driveStrength: ConfigN[Int] = None,
100+
pullMode: ConfigN[io.PullMode] = None
101+
) extends SigConstraint derives CanEqual, ReadWriter:
102+
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}"
109+
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)
116+
).filter(_.nonEmpty).mkString(", ")
117+
s"""@io($params)"""
118+
end codeString
119+
end io
84120
object io:
85-
enum IOStandard extends StableEnum, HasCodeString derives CanEqual, ReadWriter:
121+
enum Standard extends StableEnum, HasCodeString derives CanEqual, ReadWriter:
86122
case LVCMOS33, LVCMOS25, LVCMOS18
87-
def codeString(using Printer): String = "IOStandard." + this.toString
123+
def codeString(using Printer): String = "io.Standard." + this.toString
88124
enum SlewRate extends StableEnum, HasCodeString derives CanEqual, ReadWriter:
89125
case SLOW, FAST
90-
def codeString(using Printer): String = "SlewRate." + this.toString
126+
def codeString(using Printer): String = "io.SlewRate." + this.toString
91127
enum PullMode extends StableEnum, HasCodeString derives CanEqual, ReadWriter:
92128
case UP, DOWN
93-
def codeString(using Printer): String = "PullMode." + this.toString
129+
def codeString(using Printer): String = "io.PullMode." + this.toString
94130

95-
final case class IOConstraints(
96-
bitIdx: ConfigN[Int] = None,
97-
loc: ConfigN[String] = None,
98-
standard: ConfigN[IOStandard] = None,
99-
slewRate: ConfigN[SlewRate] = None,
100-
driveStrength: ConfigN[Int] = None,
101-
pullMode: ConfigN[PullMode] = None
102-
) extends Constraint derives ReadWriter:
131+
object timing:
132+
final case class ignore(bitIdx: ConfigN[Int] = None)
133+
extends SigConstraint
134+
derives CanEqual, ReadWriter:
103135
def codeString(using Printer): String =
104-
def byName[T](name: String, value: ConfigN[T]): String =
105-
value match
106-
case None => ""
107-
case cs: HasCodeString => s"$name = ${cs.codeString}"
108-
case str: String => s"$name = \"$str\""
109-
case _ => s"$name = ${value}"
110-
val params = List(
111-
byName("bitIdx", bitIdx),
112-
byName("loc", loc),
113-
byName("standard", standard),
114-
byName("slewRate", slewRate),
115-
byName("driveStrength", driveStrength),
116-
byName("pullMode", pullMode)
117-
).filter(_.nonEmpty).mkString(", ")
118-
s"""@IOConstraints($params)"""
119-
end codeString
120-
end IOConstraints
121-
end io
122-
136+
val params = bitIdx match
137+
case None => ""
138+
case bitIdx => s"bitIdx = $bitIdx"
139+
s"""@timing.ignore($params)"""
123140
end constraints

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

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import java.nio.file.Paths
1111
import dfhdl.backends
1212
import dfhdl.compiler.stages.verilog.VerilogDialect
1313
import dfhdl.compiler.stages.vhdl.VHDLDialect
14+
import dfhdl.hw.constraints
1415

1516
object Vivado extends Builder:
1617
val toolName: String = "Vivado"
@@ -27,7 +28,10 @@ object Vivado extends Builder:
2728
): CompiledDesign =
2829
addSourceFiles(
2930
cd,
30-
List(new VivadoProjectTclConfigPrinter(using cd.stagedDB.getSet).getSourceFile)
31+
List(
32+
new VivadoProjectTclConfigPrinter(using cd.stagedDB.getSet).getSourceFile,
33+
new VivadoProjectConstraintsPrinter(using cd.stagedDB.getSet).getSourceFile
34+
)
3135
)
3236
def build(
3337
cd: CompiledDesign
@@ -40,14 +44,17 @@ object Vivado extends Builder:
4044
end Vivado
4145

4246
val VivadoProjectTclConfig = SourceType.Tool("Vivado", "ProjectTclConfig")
47+
val VivadoProjectConstraints = SourceType.Tool("Vivado", "ProjectConstraints")
4348

4449
class VivadoProjectTclConfigPrinter(using getSet: MemberGetSet, co: CompilerOptions):
4550
val designDB: DB = getSet.designDB
4651
val topName: String = getSet.topName
4752
val targetLanguage: String = co.backend match
4853
case _: backends.verilog => "Verilog"
4954
case _: backends.vhdl => "VHDL"
50-
val part: String = "xc7a100tcsg324-1" // TODO: make it configurable
55+
val part: String = getSet.designDB.top.dclMeta.annotations.collectFirst {
56+
case annotation: constraints.device => annotation.name
57+
}.getOrElse(throw new IllegalArgumentException("No device annotation found"))
5158
val fileType: String = co.backend match
5259
case backend: backends.verilog => backend.dialect match
5360
case VerilogDialect.v95 | VerilogDialect.v2001 => "Verilog"
@@ -71,6 +78,7 @@ class VivadoProjectTclConfigPrinter(using getSet: MemberGetSet, co: CompilerOpti
7178
|set_property target_language $targetLanguage [current_project]
7279
|add_files -norecurse ${hdlFiles.mkString("{\n ", "\n ", "\n}")}
7380
|set_property file_type {${fileType}} [get_files *]
81+
|add_files -fileset constrs_1 -norecurse ./${topName}.xdc
7482
|######################################################################
7583
|# Suppress warnings
7684
|######################################################################
@@ -92,3 +100,109 @@ class VivadoProjectTclConfigPrinter(using getSet: MemberGetSet, co: CompilerOpti
92100
def getSourceFile: SourceFile =
93101
SourceFile(SourceOrigin.Compiled, VivadoProjectTclConfig, configFileName, contents)
94102
end VivadoProjectTclConfigPrinter
103+
104+
class VivadoProjectConstraintsPrinter(using getSet: MemberGetSet, co: CompilerOptions):
105+
val designDB: DB = getSet.designDB
106+
val topName: String = getSet.topName
107+
val constraintsFileName: String = s"$topName.xdc"
108+
val topIOs = designDB.top.members(MemberView.Folded).collect {
109+
case dcl @ DclPort() => dcl
110+
}
111+
112+
def xdcDesignConstraints: List[String] =
113+
designDB.top.dclMeta.annotations.flatMap {
114+
case deviceConstraint: constraints.device =>
115+
deviceConstraint.properties.map {
116+
case (k, v) => s"set_property $k $v [current_design]"
117+
}
118+
case _ => Nil
119+
}.toList
120+
121+
def portPattern(port: DFVal.Dcl, constraint: constraints.SigConstraint): String =
122+
val portName = port.getName
123+
constraint.bitIdx match
124+
case None => s"$portName[*]"
125+
case bitIdx => s"$portName[$bitIdx]"
126+
end portPattern
127+
128+
def xdcIOConstraint(
129+
port: DFVal.Dcl,
130+
portConstraint: constraints.io
131+
): String =
132+
var dict = ""
133+
def addToDict(key: String, value: Any): Unit =
134+
if (dict.nonEmpty)
135+
dict += " "
136+
dict += s"$key $value"
137+
138+
// Location constraint
139+
portConstraint.loc.foreach { loc =>
140+
addToDict("PACKAGE_PIN", loc)
141+
}
142+
143+
// IO standard constraint
144+
portConstraint.standard.foreach { standard =>
145+
val standardStr = standard match
146+
case constraints.io.Standard.LVCMOS33 => "LVCMOS33"
147+
case constraints.io.Standard.LVCMOS25 => "LVCMOS25"
148+
case constraints.io.Standard.LVCMOS18 => "LVCMOS18"
149+
addToDict("IOSTANDARD", standardStr)
150+
}
151+
152+
// Slew rate constraint
153+
portConstraint.slewRate.foreach { slewRate =>
154+
val slewRateStr = slewRate match
155+
case constraints.io.SlewRate.SLOW => "SLOW"
156+
case constraints.io.SlewRate.FAST => "FAST"
157+
addToDict("SLEW", slewRateStr)
158+
}
159+
160+
// Drive strength constraint
161+
portConstraint.driveStrength.foreach { driveStrength =>
162+
addToDict("DRIVE", driveStrength)
163+
}
164+
165+
// Pull mode constraint
166+
portConstraint.pullMode.foreach { pullMode =>
167+
val pullModeStr = pullMode match
168+
case constraints.io.PullMode.UP => "PULLUP"
169+
case constraints.io.PullMode.DOWN => "PULLDOWN"
170+
addToDict("PULLMODE", pullModeStr)
171+
}
172+
173+
s"set_property -dict {$dict} [get_ports {${portPattern(port, portConstraint)}}]"
174+
end xdcIOConstraint
175+
176+
def xdcTimingIgnoreConstraint(
177+
port: DFVal.Dcl,
178+
constraint: constraints.timing.ignore
179+
): String =
180+
def helper(dir: String): String =
181+
s"set_false_path $dir [get_ports {${portPattern(port, constraint)}}]"
182+
(port.modifier.dir: @unchecked) match
183+
case DFVal.Modifier.IN => helper("-from")
184+
case DFVal.Modifier.OUT => helper("-to")
185+
// 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")
187+
end xdcTimingIgnoreConstraint
188+
189+
def xdcPortConstraints(
190+
port: DFVal.Dcl
191+
): List[String] =
192+
port.meta.annotations.collect {
193+
case constraint: constraints.io => xdcIOConstraint(port, constraint)
194+
case constraint: constraints.timing.ignore => xdcTimingIgnoreConstraint(port, constraint)
195+
}
196+
end xdcPortConstraints
197+
198+
def xdcPortConstraints: List[String] =
199+
topIOs.view.flatMap(xdcPortConstraints).toList
200+
201+
def contents: String =
202+
s"""|${xdcDesignConstraints.mkString("\n")}
203+
|${xdcPortConstraints.mkString("\n")}
204+
|""".stripMargin
205+
206+
def getSourceFile: SourceFile =
207+
SourceFile(SourceOrigin.Compiled, VivadoProjectConstraints, constraintsFileName, contents)
208+
end VivadoProjectConstraintsPrinter

0 commit comments

Comments
 (0)