Skip to content

Commit 7998764

Browse files
author
Oron Port
committed
add ApplyInvertConstraint stage and fix annotation ordering
1 parent 9f9dfe8 commit 7998764

File tree

4 files changed

+334
-7
lines changed

4 files changed

+334
-7
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package dfhdl.compiler.stages
2+
3+
import dfhdl.compiler.analysis.*
4+
import dfhdl.compiler.ir.*
5+
import dfhdl.compiler.patching.*
6+
import dfhdl.options.CompilerOptions
7+
import dfhdl.core.DFValAny
8+
import dfhdl.core.DFTypeAny
9+
import dfhdl.core.DFType.asFE
10+
import scala.collection.mutable
11+
import dfhdl.internals.BitVector
12+
13+
/** This stage applies the invertActiveState constraint by inverting the port it's attached to. It
14+
* creates an intermediate variable called `{portName}_inverted` and connects it to the port.
15+
* - For output ports, the inverted intermediate variable is connected to the port.
16+
* - For input ports, the inverted input port is connected to the intermediate variable.
17+
* - All references to the port will point to the intermediate variable.
18+
* - The `invertActiveState` constraint is removed from the port.
19+
* For example,
20+
* ```
21+
* @io(invertActiveState = true)
22+
* val x1 = Bit <> IN
23+
* val y1 = Bit <> OUT
24+
* y1 <> x1
25+
* val x2 = Bit <> IN
26+
* @io(invertActiveState = true)
27+
* val y2 = Bit <> OUT
28+
* y2 <> x2
29+
* ```
30+
* will be transformed to
31+
* ```
32+
* val x1 = Bit <> IN
33+
* val x1_inverted = Bit <> VAR
34+
* x1_inverted <> !x1
35+
* val y1 = Bit <> OUT
36+
* y1 <> x1_inverted
37+
* val x2 = Bit <> IN
38+
* val y2 = Bit <> OUT
39+
* val y2_inverted = Bit <> VAR
40+
* y2 <> !y2_inverted
41+
* y2_inverted <> x2
42+
* ```
43+
*/
44+
case object ApplyInvertConstraint extends Stage:
45+
override def dependencies: List[Stage] = List()
46+
override def nullifies: Set[Stage] = Set()
47+
def transform(designDB: DB)(using getSet: MemberGetSet, co: CompilerOptions): DB =
48+
given RefGen = RefGen.fromGetSet
49+
50+
val patchList: List[(DFMember, Patch)] = designDB.members.flatMap { member =>
51+
member match
52+
case dcl: DFVal.Dcl if dcl.isPort =>
53+
val invertBitSet = mutable.BitSet.empty
54+
// Check if this port has the invertActiveState constraint
55+
dcl.meta.annotations.foreach {
56+
case constraints.IO(bitIdx = bitIdx, invertActiveState = true) =>
57+
bitIdx match
58+
case bitIdx: Int => invertBitSet += bitIdx
59+
case _ => invertBitSet ++= (0 until dcl.width)
60+
case _ =>
61+
}
62+
63+
if (invertBitSet.nonEmpty)
64+
val portName = dcl.getName
65+
val invertedVarName = s"${portName}_inverted"
66+
val updatedPortAnnots = dcl.meta.annotations.map {
67+
case ioConstraint: constraints.IO =>
68+
ioConstraint.copy(invertActiveState = None)
69+
case other => other
70+
}
71+
val updatedPortMeta = dcl.meta.copy(annotations = updatedPortAnnots)
72+
val updatedPort = dcl.copy(meta = updatedPortMeta)
73+
74+
// Create a MetaDesign to add the new members
75+
val dsn = new MetaDesign(
76+
dcl,
77+
Patch.Add.Config.ReplaceWithMemberN(1, Patch.Replace.Config.ChangeRefAndRemove)
78+
):
79+
plantMember(updatedPort)
80+
// Create the inverted intermediate variable
81+
val inversionVar =
82+
dcl.dfType.asFE[DFTypeAny].<>(VAR)(using dfc.setName(invertedVarName))
83+
84+
def invert(dfVal: DFValAny): DFValAny = dfVal.asIR.dfType match
85+
case _: DFBoolOrBit => !dfVal.asValOf[dfhdl.core.DFBit]
86+
case dfType: DFBits =>
87+
// all bits are inverted
88+
if (dfType.width == invertBitSet.size) ~dfVal.asValOf[dfhdl.core.DFBits[Int]]
89+
// otherwise, we need to use a mask
90+
else
91+
val maskStr =
92+
(for (i <- dfType.width - 1 to 0 by -1)
93+
yield
94+
if (invertBitSet.contains(i)) "1"
95+
else "0").mkString
96+
val mask = b"$maskStr"
97+
dfVal.asValOf[dfhdl.core.DFBits[Int]] ^ mask
98+
case _ => throw new RuntimeException(
99+
"Current implementation only supports constraint inversion for bits and booleans"
100+
)
101+
102+
// Create the inversion and connection based on port direction
103+
if (dcl.isPortIn)
104+
inversionVar.asDclAny <> invert(updatedPort.asValAny)
105+
else
106+
updatedPort.asDclAny <> invert(inversionVar)
107+
List(
108+
dsn.patch
109+
)
110+
else Nil
111+
end if
112+
case _ => Nil
113+
}
114+
115+
designDB.patch(patchList)
116+
end transform
117+
end ApplyInvertConstraint
118+
119+
extension [T: HasDB](t: T)
120+
def applyInvertConstraint(using co: CompilerOptions): DB =
121+
StageRunner.run(ApplyInvertConstraint)(t.db)

compiler/stages/src/main/scala/dfhdl/compiler/stages/BackendPrepStage.scala

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,29 @@ package dfhdl.compiler.stages
22

33
case object BackendPrepStage
44
extends BundleStage(
5-
DropUserOpaques, BreakOpsNoAssignments, DropUnreferencedAnons,
6-
NamedAnonMultiref, ExplicitRomVar, NamedVerilogSelection, NamedVHDLSelection,
7-
ToED, DropStructsVecs, MatchToIf, SimplifyMatchSel, DropDomains, DropMagnets,
5+
DropUserOpaques,
6+
BreakOpsNoAssignments,
7+
DropUnreferencedAnons,
8+
NamedAnonMultiref,
9+
ApplyInvertConstraint,
10+
ExplicitRomVar,
11+
NamedVerilogSelection,
12+
NamedVHDLSelection,
13+
ToED,
14+
DropStructsVecs,
15+
MatchToIf,
16+
SimplifyMatchSel,
17+
DropDomains,
18+
DropMagnets,
819
VHDLProcToVerilog,
9-
ExplicitNamedVars, DropLocalDcls, DropOutportRead, GlobalizePortVectorParams,
10-
LocalToDesignParams, DropDesignParamDeps, DropBAssignFromSeqProc, DropProcessAll,
11-
SimpleOrderMembers, ViaConnection
20+
ExplicitNamedVars,
21+
DropLocalDcls,
22+
DropOutportRead,
23+
GlobalizePortVectorParams,
24+
LocalToDesignParams,
25+
DropDesignParamDeps,
26+
DropBAssignFromSeqProc,
27+
DropProcessAll,
28+
SimpleOrderMembers,
29+
ViaConnection
1230
)
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package StagesSpec
2+
3+
import dfhdl.*
4+
import dfhdl.compiler.stages.applyInvertConstraint
5+
import dfhdl.hw.constraints.io
6+
// scalafmt: { align.tokens = [{code = "<>"}, {code = "="}, {code = "=>"}, {code = ":="}]}
7+
8+
class ApplyInvertConstraintSpec extends StageSpec:
9+
test("Basic input port inversion") {
10+
class ID extends RTDesign:
11+
@io(loc = "xloc", invertActiveState = true)
12+
val x = Bit <> IN
13+
val y = Bit <> OUT
14+
y <> x
15+
val id = (new ID).applyInvertConstraint
16+
assertCodeString(
17+
id,
18+
"""|class ID extends RTDesign:
19+
| @io(loc = "xloc")
20+
| val x = Bit <> IN
21+
| val x_inverted = Bit <> VAR
22+
| x_inverted <> (!x)
23+
| val y = Bit <> OUT
24+
| y <> x_inverted
25+
|end ID
26+
|""".stripMargin
27+
)
28+
}
29+
30+
test("Basic output port inversion") {
31+
class ID extends RTDesign:
32+
val x = Bit <> IN
33+
@io(loc = "yloc", invertActiveState = true)
34+
val y = Bit <> OUT
35+
y <> x
36+
val id = (new ID).applyInvertConstraint
37+
assertCodeString(
38+
id,
39+
"""|class ID extends RTDesign:
40+
| val x = Bit <> IN
41+
| @io(loc = "yloc")
42+
| val y = Bit <> OUT
43+
| val y_inverted = Bit <> VAR
44+
| y <> (!y_inverted)
45+
| y_inverted <> x
46+
|end ID
47+
|""".stripMargin
48+
)
49+
}
50+
51+
test("Both input and output ports inverted") {
52+
class ID extends RTDesign:
53+
@io(loc = "xloc", invertActiveState = true)
54+
val x = Bit <> IN
55+
@io(loc = "yloc", invertActiveState = true)
56+
val y = Bit <> OUT
57+
y <> x
58+
val id = (new ID).applyInvertConstraint
59+
assertCodeString(
60+
id,
61+
"""|class ID extends RTDesign:
62+
| @io(loc = "xloc")
63+
| val x = Bit <> IN
64+
| val x_inverted = Bit <> VAR
65+
| x_inverted <> (!x)
66+
| @io(loc = "yloc")
67+
| val y = Bit <> OUT
68+
| val y_inverted = Bit <> VAR
69+
| y <> (!y_inverted)
70+
| y_inverted <> x_inverted
71+
|end ID
72+
|""".stripMargin
73+
)
74+
}
75+
76+
test("Bits type inversion using bitwise NOT") {
77+
class ID extends RTDesign:
78+
@io(bitIdx = 0, loc = "xloc0", invertActiveState = true)
79+
@io(bitIdx = 1, loc = "xloc1", invertActiveState = true)
80+
@io(bitIdx = 2, loc = "xloc2", invertActiveState = true)
81+
@io(bitIdx = 3, loc = "xloc3", invertActiveState = true)
82+
val x = Bits(4) <> IN
83+
val y = Bits(4) <> OUT
84+
y <> x
85+
val id = (new ID).applyInvertConstraint
86+
assertCodeString(
87+
id,
88+
"""|class ID extends RTDesign:
89+
| @io(bitIdx = 0, loc = "xloc0")
90+
| @io(bitIdx = 1, loc = "xloc1")
91+
| @io(bitIdx = 2, loc = "xloc2")
92+
| @io(bitIdx = 3, loc = "xloc3")
93+
| val x = Bits(4) <> IN
94+
| val x_inverted = Bits(4) <> VAR
95+
| x_inverted <> (~x)
96+
| val y = Bits(4) <> OUT
97+
| y <> x_inverted
98+
|end ID
99+
|""".stripMargin
100+
)
101+
}
102+
103+
test("Bits type inversion using bitwise NOT with mask") {
104+
class ID extends RTDesign:
105+
@io(bitIdx = 0, loc = "xloc0", invertActiveState = true)
106+
@io(bitIdx = 1, loc = "xloc1")
107+
@io(bitIdx = 2, loc = "xloc2")
108+
@io(bitIdx = 3, loc = "xloc3", invertActiveState = true)
109+
val x = Bits(4) <> IN
110+
val y = Bits(4) <> OUT
111+
y <> x
112+
val id = (new ID).applyInvertConstraint
113+
assertCodeString(
114+
id,
115+
"""|class ID extends RTDesign:
116+
| @io(bitIdx = 0, loc = "xloc0")
117+
| @io(bitIdx = 1, loc = "xloc1")
118+
| @io(bitIdx = 2, loc = "xloc2")
119+
| @io(bitIdx = 3, loc = "xloc3")
120+
| val x = Bits(4) <> IN
121+
| val x_inverted = Bits(4) <> VAR
122+
| x_inverted <> (x ^ h"9")
123+
| val y = Bits(4) <> OUT
124+
| y <> x_inverted
125+
|end ID
126+
|""".stripMargin
127+
)
128+
}
129+
130+
test("Multiple ports with mixed inversion") {
131+
class ID extends RTDesign:
132+
@io(loc = "xloc1", invertActiveState = true)
133+
val x1 = Bit <> IN
134+
val y1 = Bit <> OUT
135+
@io(bitIdx = 0, loc = "xloc0", invertActiveState = false)
136+
@io(bitIdx = 1, loc = "xloc1", invertActiveState = true)
137+
@io(bitIdx = 2, loc = "xloc2", invertActiveState = true)
138+
@io(bitIdx = 3, loc = "xloc3", invertActiveState = true)
139+
val x2 = Bits(4) <> IN
140+
val y2 = Bits(4) <> OUT
141+
y1 <> x1
142+
y2 <> x2
143+
val id = (new ID).applyInvertConstraint
144+
assertCodeString(
145+
id,
146+
"""|class ID extends RTDesign:
147+
| @io(loc = "xloc1")
148+
| val x1 = Bit <> IN
149+
| val x1_inverted = Bit <> VAR
150+
| x1_inverted <> (!x1)
151+
| val y1 = Bit <> OUT
152+
| @io(bitIdx = 0, loc = "xloc0")
153+
| @io(bitIdx = 1, loc = "xloc1")
154+
| @io(bitIdx = 2, loc = "xloc2")
155+
| @io(bitIdx = 3, loc = "xloc3")
156+
| val x2 = Bits(4) <> IN
157+
| val x2_inverted = Bits(4) <> VAR
158+
| x2_inverted <> (x2 ^ h"e")
159+
| val y2 = Bits(4) <> OUT
160+
| y1 <> x1_inverted
161+
| y2 <> x2_inverted
162+
|end ID
163+
|""".stripMargin
164+
)
165+
}
166+
167+
test("Double application has no effect") {
168+
class ID extends RTDesign:
169+
@io(loc = "xloc", invertActiveState = true)
170+
val x = Bit <> IN
171+
val y = Bit <> OUT
172+
y <> x
173+
val id = (new ID).applyInvertConstraint.applyInvertConstraint
174+
assertCodeString(
175+
id,
176+
"""|class ID extends RTDesign:
177+
| @io(loc = "xloc")
178+
| val x = Bit <> IN
179+
| val x_inverted = Bit <> VAR
180+
| x_inverted <> (!x)
181+
| val y = Bit <> OUT
182+
| y <> x_inverted
183+
|end ID
184+
|""".stripMargin
185+
)
186+
}
187+
end ApplyInvertConstraintSpec

plugin/src/main/scala/plugin/MetaContextGenPhase.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase:
7373
val docOptTree = mkOptionString(docOpt)
7474
// the compiler does not transform the annotations, so we need to do it here.
7575
// See: https://github.com/scala/scala3/issues/23650
76-
val annotTree = mkList(annotations.map(a => transformAllDeep(a.tree)))
76+
// Also, we revesrse the annotations since for some reason the compiler reverses the order of the annotations.
77+
val annotTree = mkList(annotations.map(a => transformAllDeep(a.tree)).reverse)
7778
tree
7879
.select(setMetaSym)
7980
.appliedToArgs(

0 commit comments

Comments
 (0)