Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
e47f07d
compiler/stages: an async reset whose `else` is a chain gets a clock …
Aug 12, 2026
847a1a1
skills+core: two reference changes on one member want a ref filter, n…
Aug 12, 2026
86144ad
core+compiler_ir: a comparison against a `max`/`min` branch decides t…
Aug 12, 2026
e02cfc3
core+lib+plugin: the `@top` annotation moves out of the `dfhdl` wildc…
Aug 12, 2026
42c2f3a
benchmarks: pick up the `@top` import the annotation's move requires
Aug 12, 2026
0e6323c
devdocs: automatic constraints, as implemented
Aug 12, 2026
b1a646a
compiler_ir: a design parameter's default is not what the parameter i…
Aug 13, 2026
91cf1e9
core+lib: an elaboration error is named by the operation the user wrote
Aug 13, 2026
9a107ef
core+compiler_ir: a parameter the elaboration reads is no longer free…
Aug 13, 2026
02a501f
benchmarks: the VeeR-EH1 constants layer, and a formatter that aligns…
soronpo Aug 13, 2026
6f758fc
git: submodules track main, so they stop landing detached
soronpo Aug 13, 2026
9b313a7
benchmarks: the VeeR-EH1 type package
soronpo Aug 13, 2026
b214113
core: a reduction is not the binary operation that shares its symbol
soronpo Aug 13, 2026
fac6794
bugfix skill: a Func.Op is a symbol, and one symbol can name two oper…
soronpo Aug 13, 2026
e8b4fa6
verilog-to-dfhdl: the choices between equally-legal spellings, and wh…
soronpo Aug 13, 2026
37eccee
benchmarks: rvecc_encode and rvsyncss
soronpo Aug 13, 2026
df6c17f
benchmarks: rvsyncss workaround for DFHDL#485
soronpo Aug 13, 2026
4bbc37f
core: a cloned value must own its type references
soronpo Aug 13, 2026
616d732
verilog-to-dfhdl: `.reg` is a delay chain, and what it costs the form…
soronpo Aug 13, 2026
d2c68b6
compiler_ir: a parameter-bounded slice is a region the coverage can d…
soronpo Aug 13, 2026
74ff997
compiler_stages: a selection prefix is a plain reference in Verilog, …
Aug 13, 2026
e22ed00
wip: DFBitsWL/BitsHL, a bit vector with a user-defined low index
soronpo Aug 13, 2026
03f009c
BitsHL completion: simulation offsets, match rebase, composites, docs
soronpo Aug 14, 2026
f7eb9a7
BitsHL selection givens: full compile-time safety on both type paths
soronpo Aug 14, 2026
c800fe5
compiler_stages: a selection into a flattened chain's Bits link folds…
soronpo Aug 14, 2026
48b9a0c
compiler_ir: a printed bound expression cancels its additive terms as…
soronpo Aug 14, 2026
b9b5402
verilog-to-dfhdl: write the direct form, and make the control fail first
soronpo Aug 14, 2026
bf1dd14
fix sbtn detection
soronpo Aug 14, 2026
b69b79f
remove debug
soronpo Aug 14, 2026
0ee7793
add missing BitsHL print coloring
soronpo Aug 14, 2026
1a132db
scalafmt correction
soronpo Aug 14, 2026
0a00ea0
core: a constant-bound BitsHL value conforms to the matching BitsHL[H…
soronpo Aug 14, 2026
020abee
core: Sig-preserving type arithmetic restores constant-bound BitsHL s…
soronpo Aug 14, 2026
4c5e665
verilog-to-dfhdl: BitsHL works as a struct field, and the #491 caveat…
soronpo Aug 14, 2026
d04e0bd
related domains: an explicit Clk <> IN declares a derived (gated) clock
soronpo Aug 15, 2026
9ab1268
core: RTRelatedDomain / RTDerivedClkDomain / RTTransparentDomain shor…
soronpo Aug 15, 2026
ed14996
printer: a design-targeted @timing.related prints a qualified self re…
soronpo Aug 15, 2026
5fc8c8b
core: rename RTTransparentDomain to RTRegion
soronpo Aug 15, 2026
c44e65e
verilog-to-dfhdl: derived-clock composition, Vec index order, and the…
soronpo Aug 15, 2026
b39c45a
DropWholeVecAssign: element-wise lowering of whole-vector drives (#492)
soronpo Aug 15, 2026
898e35e
verilog-to-dfhdl: note that cav applies the read_slang guidance itself
claude Aug 16, 2026
3c44fba
change XYDesign into trait
soronpo Aug 16, 2026
4719836
derived clocks: same-named-only auto-connection, Clk <> OUT sources
soronpo Aug 16, 2026
f6ec4b3
new-stage: name groupByOrdered as the deterministic groupBy replacement
soronpo Aug 16, 2026
a8dbc4d
core: RTDerivedClkDomainSrc, the sourcing derived-clock shorthand
soronpo Aug 16, 2026
699593e
ir: Meta identity excludes position/doc; named comparisons replace Ca…
soronpo Aug 16, 2026
acbdb37
ir: Meta.namespace and full Meta on named DFTypes
soronpo Aug 16, 2026
deb75a0
meta: global-value namespaces, full opaque capture, doc emission on n…
soronpo Aug 16, 2026
0111302
packages: namespace-derived emission for SystemVerilog and the DFHDL …
soronpo Aug 17, 2026
7c4432e
printers: drop the t_struct_/t_enum_/t_opaque_ type-name prefixes (WY…
soronpo Aug 17, 2026
37aeebe
packages: VHDL namespace-derived package emission
soronpo Aug 17, 2026
f67d542
packages: DropPackages flattening + per-package name scoping
soronpo Aug 17, 2026
e9d163e
devdocs: packages feature implementation
soronpo Aug 17, 2026
8ee7169
devdocs: the packed/unpacked array decision, and the clk-port note wa…
soronpo Aug 17, 2026
1296f96
printers: packed arrays as the default SystemVerilog vector represent…
soronpo Aug 17, 2026
bb996f9
verilog: file header on the namespace-derived package files
soronpo Aug 17, 2026
c85c18f
core: inject a global operand's context before SimplifyFunc dereferen…
soronpo Aug 17, 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
202 changes: 198 additions & 4 deletions .claude/commands/bugfix.md

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions .claude/commands/ir-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,12 @@ DFType (sealed)
├── DFBoolOrBit (sealed)
│ ├── DFBool — boolean, width = 1
│ └── DFBit — single hardware bit, width = 1
├── DFBits(widthParamRef) — bit vector
├── DFBitsWL(widthParamRef, lowIdxRef) — bit vector with a low index (`type`-less `object DFBits`
│ is the zero-based view: apply/unapply assume/match a
│ LITERAL lowIdxRef of 0; `case dt: DFBitsWL` matches any).
│ Selection uses ABSOLUTE indices in [L, L+W-1]; selection
│ RESULTS are always zero-based. Nonzero low arises only
│ from the user-facing `BitsHL(hi, lo)` constructor.
├── DFDecimal(signed, widthParamRef, fractionWidth, nativeType)
│ ├── DFUInt(w) — unsigned integer
│ ├── DFSInt(w) — signed integer
Expand Down Expand Up @@ -774,7 +779,7 @@ case object Magnet extends Magnet // generic magnet
opaque.isMagnet // kind.isInstanceOf[Magnet]
```

**`IntParamRef`** — used for widths and indices in DFBits, DFDecimal, DFVector, ApplyRange:
**`IntParamRef`** — used for widths and indices in DFBitsWL (width + low index), DFDecimal, DFVector, ApplyRange:
```scala
opaque type IntParamRef = DFRef.TypeRef | Int
paramRef.getInt // resolve to Int (using MemberGetSet)
Expand Down
199 changes: 197 additions & 2 deletions .claude/commands/new-stage.md

Large diffs are not rendered by default.

321 changes: 318 additions & 3 deletions .claude/commands/verilog-to-dfhdl.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
[submodule "platforms"]
path = platforms
url = https://github.com/DFiantHDL/dfhdl-platforms
branch = main
[submodule "ips"]
path = ips
url = https://github.com/DFiantHDL/dfhdl-ips
branch = main
[submodule "benchmarks"]
path = benchmarks
url = https://github.com/DFiantHDL/benchmarks
branch = main
2 changes: 1 addition & 1 deletion benchmarks
Submodule benchmarks updated 56 files
+34 −0 .scalafmt.conf
+7 −7 common/BenchTable.scala
+7 −7 common/Verilator.scala
+6 −6 common/VerilatorThreadSweep.scala
+10 −10 protocol_engine/ProtocolEngine.scala
+3 −3 protocol_engine/ProtocolEngineBench.scala
+12 −12 serv/ServBench.scala
+1 −0 serv/ServantHello.scala
+1 −0 serv/ServantHelloMini.scala
+1 −0 serv/ServantPhil.scala
+18 −18 serv/serv_alu.scala
+16 −16 serv/serv_bufreg.scala
+20 −20 serv/serv_bufreg2.scala
+32 −32 serv/serv_csr.scala
+22 −22 serv/serv_ctrl.scala
+68 −68 serv/serv_decode.scala
+16 −16 serv/serv_immdec.scala
+9 −9 serv/serv_mem_if.scala
+38 −38 serv/serv_rf_if.scala
+10 −10 serv/serv_rf_ram.scala
+38 −38 serv/serv_rf_ram_if.scala
+56 −56 serv/serv_state.scala
+151 −151 serv/serv_top.scala
+26 −26 serv/servant.scala
+3 −3 serv/servant_gpio.scala
+20 −20 serv/servant_mux.scala
+11 −11 serv/servant_ram.scala
+18 −18 serv/servant_sim.scala
+7 −7 serv/servant_timer.scala
+54 −54 serv/servile.scala
+18 −18 serv/servile_arbiter.scala
+28 −28 serv/servile_mux.scala
+14 −14 serv/uart_decoder.scala
+8 −8 sha_farm/SHAFarm.scala
+5 −5 sha_farm/ShaFarmBench.scala
+69 −0 veer_eh1/LICENSE-VeeR-EH1
+119 −0 veer_eh1/README.md
+73 −0 veer_eh1/beh_lib.scala
+37 −0 veer_eh1/clk_domains.scala
+17 −0 veer_eh1/config.scala
+105 −0 veer_eh1/dec_gpr_ctl.scala
+180 −0 veer_eh1/defines.scala
+49 −0 veer_eh1/globals.scala
+231 −0 veer_eh1/lsu_clkdomain.scala
+54 −0 veer_eh1/lsu_trigger.scala
+36 −0 veer_eh1/rvbradder.scala
+82 −0 veer_eh1/rvecc_decode.scala
+27 −0 veer_eh1/rvecc_encode.scala
+20 −0 veer_eh1/rveven_paritycheck.scala
+19 −0 veer_eh1/rveven_paritygen.scala
+33 −0 veer_eh1/rvlsadder.scala
+34 −0 veer_eh1/rvmaskandmatch.scala
+34 −0 veer_eh1/rvrangecheck.scala
+22 −0 veer_eh1/rvsyncss.scala
+29 −0 veer_eh1/rvtwoscomp.scala
+335 −0 veer_eh1/veer_types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ extension [CB <: DFConditional.Block](cb: CB)(using MemberGetSet)
.toSet
selectorVal.dfType match
case _ if complexPattern => None
case dt: DFBits =>
case dt: DFBitsWL =>
if (constSet.exists(_.isBubble)) None // currently not checking don't-care patterns
else Some((1 << dt.widthIntOpt.get) == constSet.size)
case dec: DFDecimal =>
// A decimal is considered covered when all its values are covered.
// All the possible values are determined by the width of the decimal.
Some((1 << dec.widthIntOpt.get) == constSet.size)
case DFEnum(name, width, entries) =>
case DFEnum(_, _, entries) =>
// An enum is considered covered when all its entries are covered.
// Since both constant set and entries set are unique and type checking
// already confirmed, then we can safely assume that everything is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ object Eby:
def unapply(alias: DFVal.Alias.AsIs)(using MemberGetSet): Option[(DFVal, Int)] =
val relVal = alias.relValRef.get
val deltaOpt = (alias.dfType, relVal.dfType) match
case (DFUInt(toW), DFUInt(fromW)) => toW.constDiffFrom(fromW)
case (DFSInt(toW), DFSInt(fromW)) => toW.constDiffFrom(fromW)
case (DFBits(toW), DFBits(fromW)) => toW.constDiffFrom(fromW)
case _ => None
case (DFUInt(toW), DFUInt(fromW)) => toW.constDiffFrom(fromW)
case (DFSInt(toW), DFSInt(fromW)) => toW.constDiffFrom(fromW)
case (to: DFBitsWL, from: DFBitsWL) => to.widthParamRef.constDiffFrom(from.widthParamRef)
case _ => None
deltaOpt.filter(_ > 0).map((relVal, _))

// A carry-spelled arithmetic func: a binary `+`/`-`/`*` over two anonymous same-kind widening
Expand Down Expand Up @@ -315,6 +315,57 @@ object BlockRamVar:
case _ => false
case _ => false

extension (dfVal: DFVal)
//format: off
/** True for a vector-typed declaration (a variable or a named constant) whose shape and usage
* follow a MEMORY (RAM/ROM) access pattern, so a backend may keep it in a dedicated memory
* representation (e.g., a Verilog unpacked array, preserving block-RAM/ROM inference):
*
* - a port never does (it is part of the design interface)
* - an alias-bound constant (`val b = a`) never does (its value is a whole-vector read of
* its source)
* - any whole-vector use (an assignment/connection of the vector itself, a cast, a slice,
* a function argument) or any CONSTANT-index access disqualifies it
* - otherwise, a `VAR.SHARED` follows the pattern (a multi-ported RAM), and so does a
* declaration whose dynamic-index accesses include exactly one READ (a single-read
* RAM/ROM, constants included)
*
* An init reference is representation-neutral: it neither disqualifies the initialized
* declaration nor counts as a whole-vector read of the init value.
*/
//format: on
def hasMemAccessPattern(using MemberGetSet): Boolean =
def isReadAccess(dfVal: DFVal): Boolean =
dfVal.getReadDeps.exists {
case partial: DFVal.Alias.Partial => isReadAccess(partial)
case _ => true
}
def usageQualifies(isShared: Boolean): Boolean =
var wholeUse = false
var constIdx = false
var dynReads = 0
dfVal.originMembersNoTypeRef.foreach {
case idx: DFVal.Alias.ApplyIdx if idx.relValRef.get == dfVal =>
if (idx.relIdx.get.isConst) constIdx = true
else if (isReadAccess(idx)) dynReads += 1
// an init reference is representation-neutral
case dcl: DFVal.Dcl if dcl.initRefList.exists(_.get == dfVal) => // skip
case _ => wholeUse = true
}
if (wholeUse || constIdx) false
else isShared || dynReads == 1
dfVal.dfType match
case _: DFVector =>
dfVal match
case DclPort() => false
case _: DFVal.Alias => false
case dcl: DFVal.Dcl => usageQualifies(dcl.modifier.isShared)
case DclConst() => usageQualifies(isShared = false)
case _ => false
case _ => false
end hasMemAccessPattern
end extension

extension (dcl: DFVal.Dcl)
/** True when the declaration is emitted as an HDL VARIABLE (updated where it is written) rather
* than an HDL SIGNAL (updated only once the enclosing process suspends). The classification is
Expand Down Expand Up @@ -425,15 +476,15 @@ extension (dfVal: DFVal)
case DFVal.Alias.ApplyIdx.ConstIdx(i) =>
val maxValueOpt = relVal.dfType match
case vector: DFVector => vector.lengthIntOpt
case bits: DFBits => bits.widthIntOpt
case bits: DFBitsWL => bits.widthIntOpt
case xInt: DFDecimal => xInt.widthIntOpt
case _ => None
val padMaxValue = maxValueOpt.getOrElse(100) - 1
s"_${i.toPaddedString(padMaxValue)}"
case _ => "_sel"
case applyRange: DFVal.Alias.ApplyRange =>
applyRange.dfType.runtimeChecked match
case DFBits(_) | DFUInt(_) | DFSInt(_) =>
case (_: DFBitsWL) | DFUInt(_) | DFSInt(_) =>
val padMaxValue = applyRange.widthIntOpt.getOrElse(100) - 1
val idxHigh =
applyRange.idxHighRef.getIntOpt.map(_.toPaddedString(padMaxValue)).getOrElse("hi")
Expand Down Expand Up @@ -469,7 +520,8 @@ extension (dfVal: DFVal)
// looking for what kind of type reference it is
r.originMember.asInstanceOf[DFVal].dfType match
case DFVector(_, (cellDimRef: TypeRef) :: _) if cellDimRef == r => Some("length")
case DFBits(widthRef: TypeRef) if widthRef == r => Some("width")
case dt: DFBitsWL if dt.widthParamRef.getRef.contains(r) => Some("width")
case dt: DFBitsWL if dt.lowIdxRef.getRef.contains(r) => Some("lowidx")
case DFDecimal(magnitudeWidthParamRef = widthRef: TypeRef) if widthRef == r =>
Some("width")
case _ => None
Expand Down Expand Up @@ -685,10 +737,16 @@ class ComposedDFTypeReplacement[H](
def unapply(dfType: DFType): Option[DFType] =
val composed = dfType match
case dt: DFStruct =>
val updatedMap = ListMap.from(dt.fieldMap.view.collect { case (name, Extractor(dfType)) =>
(name, dfType)
// every field is kept — only the matching ones are replaced. Collecting just the
// matches here would DROP the fields the extractor does not apply to.
var anyUpdated = false
val updatedMap = ListMap.from(dt.fieldMap.view.map {
case (name, Extractor(dfType)) =>
anyUpdated = true
(name, dfType)
case entry => entry
})
if (updatedMap.nonEmpty) Some(dt.copy(fieldMap = updatedMap))
if (anyUpdated) Some(dt.copy(fieldMap = updatedMap))
else None
case dt: DFOpaque =>
dt.actualType match
Expand All @@ -711,7 +769,7 @@ extension (lhs: DFVal)(using MemberGetSet)
// total-width ref: for integer decimals the magnitude ref is the total ref (and may be
// parametric); fixed-point total widths are always constant
def widthRef(v: DFVal): IntParamRef = (v.dfType: @unchecked) match
case dt: DFBits => dt.widthParamRef
case dt: DFBitsWL => dt.widthParamRef
case dt: DFDecimal if dt.fractionWidth == 0 => dt.magnitudeWidthParamRef
case dt: DFDecimal => IntParamRef(dt.widthUNSAFE)
widthRef(lhs).compare(widthRef(rhs))(func)
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package dfhdl.compiler
package analysis
import ir.*
import scala.collection.mutable

/** Which HDL-method blocks (ED methods / static functions) are emitted ONCE in the shared globals
* area (a VHDL package / a Verilog defs header) instead of inlined in each using design.
*
* This is a PLACEMENT decision computed purely from the IR, and both the printers (which emit the
* shared area) and `DropPackages` (which flattens the namespaces of everything placed there when
* the backend has no packages) must agree on it, so it lives here as one definition rather than in
* either of them. A backend may only WIDEN it (VHDL additionally globalizes a static function read
* by a port declaration, since the entity is elaborated before the architecture) by overriding
* `Printer.globalHDLMethods`.
*
* These read the design members directly, so they expect a FLAT DB (`newToOld`): the printers are
* fed one, and a stage running on the hierarchical root must flatten first.
*/
extension (designDB: DB)
/** The body members of an HDL-method block: the members it owns. */
def methodBodyMembers(m: DFDesignBlock): List[DFMember] =
designDB.designMemberTable.getOrElse(m, Nil)

/** HDL-method blocks mapped to the set of NON-method designs that use them. A method call is
* owned by the design (or method) whose body makes the call (`designBlockOwnershipMap`); a
* method-to-method call is resolved transitively, so the resulting users are always real
* designs.
*/
private def hdlMethodDesignUsers: Map[DFDesignBlock, Set[DFDesignBlock]] =
val ownership = designDB.designBlockOwnershipMap
def realUsersOf(block: DFDesignBlock, seen: Set[DFDesignBlock]): Set[DFDesignBlock] =
ownership.getOrElse(block, Set.empty).flatMap { owner =>
if (!owner.isHDLMethod) Set(owner)
else if (seen(owner)) Set.empty[DFDesignBlock]
else realUsersOf(owner, seen + owner)
}
ownership.keysIterator.filter(_.isHDLMethod)
.map(m => m -> realUsersOf(m, Set(m))).toMap

/** An HDL method is emittable in a shared package/header only if its body references no value
* captured from a single design. Captures materialize as PHANTOM input ports (globals are never
* captured — they are reachable everywhere and referenced directly), so a method with any
* phantom input is inherently design-local and stays inlined there.
*/
def methodIsGlobalEligible(m: DFDesignBlock)(using MemberGetSet): Boolean =
// every call of `m`, global-scope calls included (`members` covers the globals)
def callSitesOf(m: DFDesignBlock): List[DFVal.Func] =
designDB.members.collect {
case DFVal.Func.Call(call, key) if key.getDesignBlock == m => call
}
val formals = designDB.methodBodyMembers(m).collect {
case dcl: DFVal.Dcl if dcl.isPortIn => dcl
}
val phantomIdxs = formals.view.zipWithIndex.collect { case (f, i) if f.isPhantom => i }.toList
// A capture materializes as a PHANTOM input port, whose actual is bound POSITIONALLY at
// each call site. A GLOBAL actual is reachable from the shared package/header, so it keeps
// the method eligible; a design-local one pins the method to its design. An actual that
// cannot be lined up with the formals is treated as design-local (the conservative answer).
phantomIdxs.isEmpty || callSitesOf(m).forall { call =>
val actuals = call.args.map(_.get)
actuals.length == formals.length && phantomIdxs.forall { i =>
actuals(i) match
case dfVal: DFVal.CanBeGlobal => dfVal.isGlobal
case _ => false
}
}
end methodIsGlobalEligible

/** Expand a set of HDL-method blocks to include everything they transitively call: an emitted
* method's body calls them, and a shared package/header function cannot call one that is
* declared inside a single design (or, for a method reached only from global scope, not declared
* at all).
*/
def methodCallClosure(seeds: Set[DFDesignBlock])(using MemberGetSet): Set[DFDesignBlock] =
val result = mutable.Set.empty[DFDesignBlock]
def visit(m: DFDesignBlock): Unit =
if (result.add(m))
designDB.methodBodyMembers(m).foreach {
case DFVal.Func.Call(_, key) =>
val callee = key.getDesignBlock
if (callee.isHDLMethod) visit(callee)
case _ =>
}
seeds.foreach(visit)
result.toSet

/** HDL-method blocks referenced by a GLOBAL `Func` call (a static function called at global
* scope, e.g. to compute a global constant). Such a method has no design user, but must still be
* emitted once in the shared globals area alongside the global value it computes.
*/
private def globalCallMethods(using MemberGetSet): Set[DFDesignBlock] =
designDB.membersGlobals.view.collect {
case DFVal.Func.Call(_, key) => key.getDesignBlock
}.filter(_.isHDLMethod).toSet

/** HDL-method blocks emitted once in the shared globals area: used by more than one design, or
* called from global scope; and package-eligible.
*/
def globalHDLMethods(using MemberGetSet): Set[DFDesignBlock] =
val byUsage = designDB.hdlMethodDesignUsers.iterator.collect {
case (m, users) if users.sizeIs > 1 => m
}
designDB.methodCallClosure(byUsage.toSet ++ designDB.globalCallMethods)
.filter(designDB.methodIsGlobalEligible)
end extension
Loading
Loading