Skip to content

Commit 1ccda7a

Browse files
author
Oron Port
committed
feat: Improve type checking and error handling in DFIf and DFMatch
This commit enhances type compatibility checks and error reporting for conditional expressions: - Add `isSimilarTo` method for more flexible type comparisons in DFIf and DFMatch - Implement detailed error logging for match expressions with different branch types - Improve type resolution logic to handle parametric width scenarios - Add test cases for type width mismatches in match expressions
1 parent 12c24f3 commit 1ccda7a

File tree

4 files changed

+99
-11
lines changed

4 files changed

+99
-11
lines changed

compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,4 +866,53 @@ class PrintCodeStringSpec extends StageSpec:
866866
|""".stripMargin
867867
)
868868
}
869+
test("If printing with parametric width") {
870+
class IfWithParams(
871+
val width: Int <> CONST = 8
872+
) extends DFDesign:
873+
val sel = Bit <> IN
874+
val iBits = Bits(width) <> IN
875+
val oBits = Bits(width) <> VAR
876+
oBits := (if (sel) iBits else all(0))
877+
val top = (new IfWithParams).getCodeString
878+
assertNoDiff(
879+
top,
880+
"""|class IfWithParams(val width: Int <> CONST = 8) extends DFDesign:
881+
| val sel = Bit <> IN
882+
| val iBits = Bits(width) <> IN
883+
| val oBits = Bits(width) <> VAR
884+
| oBits := ((
885+
| if (sel) iBits
886+
| else b"0".repeat(width)
887+
| ): Bits[width.type] <> VAL)
888+
|end IfWithParams""".stripMargin
889+
)
890+
}
891+
test("Match printing with parametric width") {
892+
class MatchWithParams(
893+
val width: Int <> CONST = 8
894+
) extends DFDesign:
895+
val sel = Bit <> IN
896+
val iBits = Bits(width) <> IN
897+
val oBits = Bits(width) <> VAR
898+
oBits := sel match
899+
case 0 => all(0)
900+
case 1 => iBits
901+
end MatchWithParams
902+
val top = (new MatchWithParams).getCodeString
903+
assertNoDiff(
904+
top,
905+
"""|class MatchWithParams(val width: Int <> CONST = 8) extends DFDesign:
906+
| val sel = Bit <> IN
907+
| val iBits = Bits(width) <> IN
908+
| val oBits = Bits(width) <> VAR
909+
| oBits := ((
910+
| sel match
911+
| case 0 => b"0".repeat(width)
912+
| case 1 => iBits
913+
| end match
914+
| ): Bits[width.type] <> VAL)
915+
|end MatchWithParams""".stripMargin
916+
)
917+
}
869918
end PrintCodeStringSpec

core/src/main/scala/dfhdl/core/DFIf.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ object DFIf:
8585
def fromBranches[R](
8686
branches: List[(DFValOf[DFBoolOrBit], () => R)],
8787
elseOption: Option[() => R]
88-
)(using DFC): R = try
88+
)(using dfc: DFC): R = try
89+
import dfc.getSet
8990
val header = Header(DFUnit)
9091
val dfcAnon = summon[DFC].anonymize
9192
var branchTypes = List.empty[ir.DFType]
@@ -110,7 +111,7 @@ object DFIf:
110111
val hasNoType = branchTypes.contains(ir.DFUnit)
111112
// if one branch has DFUnit, the return type is DFUnit.
112113
// otherwise, all types must be the same.
113-
if (hasNoType || branchTypes.allElementsAreEqual)
114+
if (hasNoType || branchTypes.forall(_.isSimilarTo(branchTypes.head)))
114115
val retDFType = if (hasNoType) ir.DFUnit else branchTypes.head
115116
val DFVal(headerIR: DFIfHeader) = header: @unchecked
116117
val headerUpdate = headerIR.copy(dfType = retDFType)

core/src/main/scala/dfhdl/core/DFMatch.scala

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ object DFMatch:
5252
selector: DFValAny,
5353
cases: List[(Pattern, Option[DFValOf[DFBool]], () => R)],
5454
forceAnonymous: Boolean
55-
)(using DFC): R = try
55+
)(using dfc: DFC): R = try
56+
import dfc.getSet
5657
val dfcAnon = summon[DFC].anonymize
5758
val header =
5859
Header(DFUnit, selector)(using if (forceAnonymous) dfcAnon else dfc)
@@ -61,23 +62,45 @@ object DFMatch:
6162
val firstCaseRun: () => R = () =>
6263
firstCaseRet = Some(cases.head._3())
6364
firstCaseRet.get
65+
var dfTypes = List.empty[ir.DFType]
66+
var typesAreSimilar = true
6467
val firstCase =
6568
singleCase(cases.head._1, cases.head._2, header, firstCaseRun)
69+
dfTypes = firstCase._1.asIR :: dfTypes
6670
val (retDFType, _) =
6771
cases.drop(1).foldLeft(firstCase) { case ((prevDFType, prevBlock), curCase) =>
6872
val (dfType, block) =
6973
singleCase(curCase._1, curCase._2, prevBlock, curCase._3)(using dfcAnon)
74+
dfTypes = dfType.asIR :: dfTypes
7075
val commonDFType =
71-
if (dfType.asIR == prevDFType.asIR) prevDFType else DFUnit
76+
if (dfType.asIR.isSimilarTo(prevDFType.asIR)) prevDFType
77+
else
78+
typesAreSimilar = false
79+
DFUnit
7280
(commonDFType, block)
7381
}
74-
retDFType match
75-
case DFUnit => firstCaseRet.get
76-
case _ =>
77-
val DFVal(headerIR: DFMatchHeader) = header: @unchecked
78-
val headerUpdate = headerIR.copy(dfType = retDFType.asIR)
79-
// updating the type of the if header
80-
headerIR.replaceMemberWith(headerUpdate).asValAny.asInstanceOf[R]
82+
if (typesAreSimilar)
83+
retDFType match
84+
case DFUnit => firstCaseRet.get
85+
case _ =>
86+
val DFVal(headerIR: DFMatchHeader) = header: @unchecked
87+
val headerUpdate = headerIR.copy(dfType = retDFType.asIR)
88+
// updating the type of the match header
89+
headerIR.replaceMemberWith(headerUpdate).asValAny.asInstanceOf[R]
90+
else
91+
given printer: Printer = DefaultPrinter(using dfc.getSet)
92+
val err = DFError.Basic(
93+
"match",
94+
new IllegalArgumentException(
95+
s"""|This DFHDL `match` expression has different return types for cases.
96+
|These are its branch types in order:
97+
|${dfTypes.view.reverse.map(t => printer.csDFType(t)).mkString("\n")}
98+
|""".stripMargin
99+
)
100+
)
101+
dfc.logError(err)
102+
err.asVal[DFTypeAny, ModifierAny].asInstanceOf[R]
103+
end if
81104
catch case e: DFError => DFVal(DFError.Derived(e)).asInstanceOf[R]
82105
end fromCases
83106

core/src/test/scala/CoreSpec/DFMatchSpec.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,19 @@ class DFMatchSpec extends DFSpec:
159159
}
160160
}
161161
}
162+
163+
test("Different return widths error") {
164+
assertRuntimeErrorLog(
165+
"""|This DFHDL `match` expression has different return types for cases.
166+
|These are its branch types in order:
167+
|Bits(2)
168+
|Bits(3)
169+
|""".stripMargin
170+
) {
171+
val res: Bits[Int] <> VAL =
172+
i match
173+
case 0 => b"11"
174+
case 1 => b"111"
175+
}
176+
}
162177
end DFMatchSpec

0 commit comments

Comments
 (0)