Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1cdeadd
simplify the basic lib playground
Aug 9, 2026
54e549c
core+docs: a wildcard `Int`'s minimum width counts as an actual width…
Aug 9, 2026
3548169
core: revise a sub-design instance's port through its by-name selecti…
Aug 9, 2026
3b844e7
ir+lib+compiler_stages: connection flow never defers to parameters (#…
Aug 10, 2026
a86c482
ir+compiler_stages: a shared-variable writer emits `always` instead o…
Aug 10, 2026
3e0d457
compiler_stages+docs: a port-referenced constant is declared in the p…
Aug 10, 2026
3040d5c
skills: record the two backend-legality lessons from #472/#473
Aug 10, 2026
ec89141
core+plugin: the frontend namespace object is public, named `__hdl`
Aug 10, 2026
734c4e8
skills: record the inaccessible-prefix species from #468
Aug 10, 2026
b123970
update benchmarks formatting
Aug 10, 2026
958e2fb
ir+core+docs: single-bit arithmetic; bitwise ops check parametric wid…
Aug 11, 2026
dc5ff6f
skills: record the two width-rule species from #476/#474
Aug 11, 2026
e9d50d2
compiler: static assertions as concurrent design contracts
Aug 11, 2026
10ca4ab
skill: format before the final full-suite run
Aug 11, 2026
7778923
compiler: name a text output through its `val` binding
Aug 11, 2026
9c31526
core+compiler: auto value constraints for unprovable width fits
Aug 11, 2026
09fbc01
skill: shut the sbt server down on a GC-time warning
Aug 11, 2026
826d215
core+compiler: minimize auto constraints against each other and the d…
Aug 11, 2026
0d3695c
compiler+lib: fix the Verilog-95 signed ordering macros and cover the…
Aug 11, 2026
a322eca
docs: how to compare values of different widths
Aug 11, 2026
2714d5d
core: `.extend` and `.truncate` width-adjustment tags for `UInt`/`SInt`
Aug 11, 2026
4610e44
wip: auto constraints for undecidable wildcard fits, messages via `Ch…
Aug 11, 2026
cc47f27
core: `.extend` and `.truncate` for `Bits`, disambiguated by target name
Aug 11, 2026
2070a06
core: the width-adjustment permissions relax the checks they cover
Aug 11, 2026
447713c
test: a width-adjustment permission over a design parameter
Aug 11, 2026
2b19e14
core: a comparison lets its permission-carrying operand adapt, either…
Aug 12, 2026
ed3720a
core+docs: migrate the argument-less `.resize` to `.extend` and `.tru…
Aug 12, 2026
f00dc86
platforms: bump to the `.truncate` migration
Aug 12, 2026
c689fc8
core+docs: width diagnostics name the adjustment that actually applies
Aug 12, 2026
defb89c
core: a width-adjustment permission survives the `Bits`/integer conve…
Aug 12, 2026
4f3d374
core: deprecate the argument-less `.resize`
Aug 12, 2026
99e7a0b
update version dependencies
Aug 12, 2026
6ee2c3e
core+docs: a wildcard `Int` operand widens to the target, not to the …
Aug 12, 2026
eb5bc03
core: a wildcard `Int` parameter states the value bound its adaptatio…
Aug 12, 2026
22ddf00
git: line endings are LF in the working tree, not just the index
Aug 12, 2026
42cac8f
submodules: bump to the LF line-ending policy
Aug 12, 2026
ad5423f
scalafmt update
Aug 12, 2026
ce316f4
core: a `.truncate` decides against target-context widening, and a wi…
Aug 12, 2026
ed7b357
internals+options+tools: a console reset and ANSI color need a termin…
Aug 12, 2026
0e75838
core: an LHS-dominant operation states the fit it needs rather than r…
Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 223 additions & 8 deletions .claude/commands/bugfix.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .claude/commands/new-stage.md
Original file line number Diff line number Diff line change
Expand Up @@ -1322,7 +1322,7 @@ abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false)
patch list when the Add's members reference the replaced instance — ref-table effects apply
in list order, so the Add's references then resolve to the replacement.
19. **Name shadowing inside MetaDesign bodies** — `MetaDesign` extends `Design`, whose
`export dfhdl.hdl.*` brings frontend names (`DFVal`, `StepBlock`, …) into the *class* scope,
`export dfhdl.__hdl.*` brings frontend names (`DFVal`, `StepBlock`, …) into the *class* scope,
shadowing the file-level `import dfhdl.compiler.ir.*` wildcard for overlapping names. Inside
a MetaDesign body, add `import dfhdl.compiler.ir` at the file top and qualify IR types as
`ir.DFVal`, `ir.StepBlock`, etc., importing only the core names actually needed (e.g.
Expand Down
18 changes: 18 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Line endings are LF everywhere: in the repository and in the working tree.
# These rules override each developer's core.autocrlf / core.eol settings, so
# checkouts are byte-identical on every platform.
* text=auto eol=lf

# Windows batch scripts are the exception: cmd.exe wants CRLF.
*.bat text eol=crlf
*.cmd text eol=crlf

# Binary formats. Git auto-detects these, but be explicit so no heuristic can
# misfire and corrupt them with an EOL conversion.
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.pdf binary
*.zip binary
*.jar binary
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = 3.11.4
version = 3.11.5
runner.dialect = scala3

maxColumn = 100
Expand Down
2 changes: 1 addition & 1 deletion benchmarks
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ val vgaMonitorVersion = "1.0.1"
val interactiveSimVersion = "0.4.2"
// dependency versions
val scodecVersion = "1.2.5"
val munitVersion = "1.3.4"
val airframelogVersion = "2026.1.7"
val munitVersion = "1.3.5"
val airframelogVersion = "2026.2.2"
val oslibVersion = "0.11.8"
val scallopVersion = "6.0.0"
val upickleVersion = "4.4.3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,16 @@ extension (dfVal: DFVal)
case _ => false
}

/** Part of the anonymous cone computing a static assertion's condition or message, which must
* therefore be placed with it (see `TextOut.isStaticAssert`).
*/
def isReferencedByAnyStaticAssert(using MemberGetSet): Boolean =
dfVal.originMembers.view.exists {
case textOut: TextOut => textOut.isStaticAssert
case dfVal: DFVal => dfVal.isReferencedByAnyStaticAssert
case _ => false
}

@tailrec private def flatName(member: DFVal, suffix: String)(using MemberGetSet): String =
member match
case named if !named.isAnonymous => s"${member.getName}$suffix"
Expand Down Expand Up @@ -578,6 +588,27 @@ extension (textOut: TextOut)
.collect { case DFRef(dfVal: DFVal) => dfVal }
.flatMap(_.collectRelMembers(false)).toList

/** A static assertion: an assertion placed directly in a domain body (a design body or a `domain`
* body) whose guard and message arguments are all constant. It states an elaboration-time
* contract of the design rather than a runtime condition, so it stays a concurrent body
* statement through the lowering to ED and prints as an elaboration-time construct.
*
* The position is part of the definition: an assertion nested in a process, a conditional block
* or a loop is procedural content and is never static, whatever its guard, since the
* elaboration-time forms only exist in concurrent position.
*/
def isStaticAssert(using MemberGetSet): Boolean =
textOut.op match
case TextOut.Op.Assert(assertionRef, _) =>
val isConcurrent = textOut.getOwner match
// an HDL method body is procedural, not a concurrent body
case dsn: DFDesignBlock => !dsn.isHDLMethod
case _: DFDomainOwner => true
case _ => false
isConcurrent && assertionRef.get.isConst && textOut.msgArgs.forall(_.get.isConst)
case _ => false
end extension

extension (member: DFMember)
private def isPublicMember(using MemberGetSet): Boolean =
member match
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ extension (pb: ProcessBlock)(using MemberGetSet)
// stages to decide between the `ToED` reset-branch path and declaration-init forms.
def hasResolvedRstCfg: Boolean =
pb.getOwnerDomain.resolvedRstAnnot.isDefined
// True when the process writes (any part of) a shared variable. A shared variable models a
// multi-ported memory, so it is written from as many processes as it has write ports, and a
// single-driver process construct (SystemVerilog `always_ff`) therefore cannot render such a
// process. Reads are unconstrained and do not count. The whole nesting is searched, since a
// write can sit inside a conditional or a loop within the process.
def writesSharedVar: Boolean =
pb.members(MemberView.Flattened).exists {
case DFNet.Assignment(toVal, _) =>
toVal.departialDcl.exists((dcl, _) => dcl.modifier.isShared)
case _ => false
}
end extension

// The declarations assigned by the given block members, ordered by first assignment.
Expand Down
52 changes: 46 additions & 6 deletions compiler/ir/src/main/scala/dfhdl/compiler/ir/ConnectToMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@ object ConnectToMap:
extension (ctm: ConnectToMap)(using MemberGetSet)
def connectToVals: Set[ConnectToVal] = ctm.keySet

/** All nets whose slice overlaps `slice` on `connectToVal`, each with its overlap verdict:
* `Tri.Yes` for a proven overlap, `Tri.Unknown` when the relation could not be proven either
* way (conservatively included). Provably disjoint nets are excluded.
/** All nets whose slice overlaps `slice` on `connectToVal`, each with the slice it was stored
* under and its overlap verdict: `Tri.Yes` for a proven overlap, `Tri.Unknown` when the
* relation could not be proven either way (conservatively included). Provably disjoint nets
* are excluded. The stored slice is carried so a legality check can re-decide an `Unknown`
* verdict at the elaborated parameter values (see [[ConnectToMap.foldedOverlap]]).
*/
def getNetsVerdicts(connectToVal: ConnectToVal, slice: Slice): Vector[(DFNet, Tri)] =
def getNetsVerdicts(connectToVal: ConnectToVal, slice: Slice): Vector[(DFNet, Slice, Tri)] =
ctm.get(connectToVal) match
case Some(entry) =>
val widthOpt = connectToVal.widthIntOpt
entry.nets.view
.map { (storedSlice, net) =>
(net, ConnectToMap.overlapsSlices(storedSlice, slice, widthOpt))
(net, storedSlice, ConnectToMap.overlapsSlices(storedSlice, slice, widthOpt))
}
.filter(_._2 != Tri.No)
.filter(_._3 != Tri.No)
.toVector
case None => Vector.empty

Expand All @@ -37,6 +39,18 @@ object ConnectToMap:
*/
def getNets(connectToVal: ConnectToVal, slice: Slice): Set[DFNet] =
getNetsVerdicts(connectToVal, slice).view.map(_._1).toSet

/** Is some net's write to `slice` PROVEN to overlap? Unlike [[contains]], a merely unproven
* (parameter-dependent) relation answers `false`. Directionality decisions use this: which end
* of a connection is the sink is a structural property that must hold for every parameter
* assignment, so parameter opacity may never flip it.
*/
def hasProvenNet(connectToVal: ConnectToVal, slice: Slice): Boolean =
getNetsVerdicts(connectToVal, slice).exists(_._3 == Tri.Yes)
def hasProvenNet(dfVal: DFVal): Boolean =
dfVal.departialPBNS match
case Some(connectToVal, slice) => hasProvenNet(connectToVal, slice)
case _ => false
def getNets(dfVal: DFVal): Set[DFNet] =
dfVal.departialPBNS match
case Some(connectToVal, slice) => getNets(connectToVal, slice)
Expand Down Expand Up @@ -67,6 +81,32 @@ object ConnectToMap:
ctm.get(connectToVal).map(_.coverage).getOrElse(Coverage.empty)
end extension

/** Re-decides an overlap the symbolic proofs left [[Tri.Unknown]], by folding both slices at the
* elaborated parameter values (an elaboration root's own parameters resolve through their
* defaults). `Some(true)` for a collision at those values, `Some(false)` when disjoint there,
* and `None` when either endpoint does not fold, in which case the caller skips its check.
*
* LEGALITY ONLY, never directionality: unlike [[overlapsSlices]], a verdict here holds for the
* parameters actually elaborated rather than for every HDL parameter override. See
* [[IntExprCalc.DataCalc.foldConst]].
*/
def foldedOverlap(a: Slice, b: Slice)(using MemberGetSet): Option[Boolean] =
for
ra <- foldedRange(a)
rb <- foldedRange(b)
yield ra.intersect(rb).nonEmpty

private def foldedRange(slice: Slice)(using MemberGetSet): Option[Range] = slice match
case Slice.Concrete(r) => Some(r)
case Slice.Symbolic(lo, w) =>
for
loInt <- IntExprCalc.DataCalc.foldConst(lo)
wInt <- IntExprCalc.DataCalc.foldConst(w)
yield Range(loInt, loInt + wInt)
// `Full` and `Unknown` never reach here: every pairing of `Full` is already decided by
// `overlapsSlices`, and `Unknown` carries nothing to fold.
case _ => None

/** Pairwise slice-overlap predicate used by `getNets`. Returns `Tri.Yes` only when provably
* overlapping, `Tri.No` only when provably disjoint, `Tri.Unknown` otherwise.
*/
Expand Down
Loading
Loading