diff --git a/.claude/commands/ir-reference.md b/.claude/commands/ir-reference.md new file mode 100644 index 000000000..fb0729542 --- /dev/null +++ b/.claude/commands/ir-reference.md @@ -0,0 +1,992 @@ +# DFHDL IR Reference + +> **For contributors working with the DFHDL compiler internals.** +> This skill is version-controlled alongside the codebase — keep it updated when IR types or analysis helpers change. + +Complete reference for the IR (intermediate representation) layer at +`compiler/ir/src/main/scala/dfhdl/compiler/ir/` and the analysis helpers at +`compiler/ir/src/main/scala/dfhdl/compiler/analysis/`. + +This is the data model that every stage `transform` method pattern-matches on and patches. +See `/new-stage` for how to write a stage that uses this reference. + +--- + +## Top-level sealed hierarchy + +``` +DFMember (sealed) +├── DFMember.Empty — sentinel / placeholder (no owner, no meta) +├── DFVal — any value in the design +│ ├── DFVal.Const — literal constant +│ ├── DFVal.Dcl — port / variable / const declaration +│ ├── DFVal.Func — computed expression / operator +│ ├── DFVal.Alias — alias / cast / partial selection +│ │ ├── DFVal.Alias.AsIs — type cast (.as(T) / .actual) +│ │ ├── DFVal.Alias.History — prev / pipe (.prev, .reg) +│ │ ├── DFVal.Alias.ApplyRange — bit-range slice (x(hi, lo)) +│ │ ├── DFVal.Alias.ApplyIdx — vector / bits indexing (x(i)) +│ │ └── DFVal.Alias.SelectField — struct field (x.fieldName) +│ ├── DFVal.DesignParam — design-level parameter reference +│ ├── DFVal.PortByNameSelect — port selected by string path +│ └── DFVal.Special — NOTHING | OPEN | CLK_FREQ +├── Statement — executable statements +│ ├── DFNet — assignment / connection +│ ├── Wait — process wait +│ ├── TextOut — print / assert / finish +│ └── Goto — FSM step jump +├── DFBlock — containers for child members +│ ├── DFDesignBlock — module / design definition +│ ├── DomainBlock — clock-domain grouping +│ ├── ProcessBlock — always / process block +│ ├── StepBlock — FSM step +│ ├── DFConditional.Block — if / match clause +│ │ ├── DFIfElseBlock +│ │ └── DFCaseBlock +│ └── DFLoop.Block — loop body +│ ├── DFForBlock +│ └── DFWhileBlock +├── DFConditional.Header — if / match header expression +│ ├── DFIfHeader +│ └── DFMatchHeader +├── DFInterfaceOwner — interface abstraction +└── DFRange — for-loop range +``` + +Every `DFMember` carries three common fields: + +| Field | Type | Purpose | +|---|---|---| +| `ownerRef` | `DFOwner.Ref` | Points to the enclosing owner/block | +| `meta` | `Meta` | Name, source position, doc, annotations | +| `tags` | `DFTags` | Extensible tag map | + +--- + +## DFMember — common API + +```scala +// Navigation (all require using MemberGetSet) +m.getOwner // immediate owner (throws if global) +m.getOwnerBlock // nearest enclosing DFBlock +m.getOwnerDesign // nearest enclosing DFDesignBlock +m.getOwnerDomain // nearest DFDomainOwner (design, domainBlock) +m.getOwnerProcessBlock // nearest ProcessBlock +m.getOwnerStepBlock // nearest StepBlock +m.getThisOrOwnerDesign // this if DFDesignBlock, else getOwnerDesign +m.getThisOrOwnerDomain // this if DFDomainOwner, else getOwnerDomain +m.isMemberOf(owner) // direct child of owner? +m.isInsideOwner(owner) // anywhere inside owner at any depth? +m.isSameOwnerDesignAs(that) // same immediate design? +m.getOwnerChain // List[DFBlock] from root to direct owner + +// Domain checks (extension, compiler.ir package) +m.getDomainType // DomainType of nearest domain owner +m.isInDFDomain // true iff DF domain +m.isInRTDomain // true iff RT domain +m.isInEDDomain // true iff ED domain +m.isInProcess // true iff inside a ProcessBlock + +// Tag helpers (extension) +m.getTagOf[MyTag] // Option[MyTag] +m.hasTagOf[MyTag] // Boolean + +// Meta helpers (on DFMember.Named) +m.isAnonymous // meta.nameOpt.isEmpty +m.getName // resolved name string +m.getFullName // fully-qualified owner.owner.name +m.getRelativeName(callOwner) // shortest unambiguous name from callOwner + +// References +m.getRefs // List[DFRef.TwoWayAny] — all two-way refs in this member +m.copyWithNewRefs // deep copy with fresh reference IDs (using RefGen) +``` + +--- + +## DFVal — value subtypes + +### DFVal trait (common) + +```scala +dfVal.dfType // DFType +dfVal.width // Int (delegates to dfType) +dfVal.isGlobal // true when ownerRef points to DFMember.Empty +dfVal.isAnonymous // true when meta.nameOpt.isEmpty +dfVal.isFullyAnonymous // true when this and all its arg deps are anonymous +dfVal.isConst // true when getConstData.nonEmpty +dfVal.getConstData // Option[Any] — constant-fold result (cached) +dfVal.isSimilarTo(that) // semantic equivalence (type + data, ignoring identity) +dfVal.updateDFType(t) // create copy with new DFType +``` + +**Sub-trait markers:** +- `DFVal.CanBeExpr` — can appear as an expression (Const, Func, Alias, DesignParam, Special) +- `DFVal.CanBeGlobal` — can live at top-level scope (Const, Func, Alias.Partial) + +**Extension methods on any DFVal:** + +```scala +dfVal.isPort // Dcl with IN/OUT/INOUT modifier +dfVal.isPortIn // Dcl with IN modifier +dfVal.isPortOut // Dcl with OUT modifier +dfVal.isVar // Dcl with VAR modifier +dfVal.isReg // Dcl with REG special modifier +dfVal.isOpen // DFVal.Special(OPEN) +dfVal.isDesignParam // DFVal.DesignParam instance +dfVal.dealias // follow alias chain → Option[DFVal.Dcl | DFVal.Special] +dfVal.departial // strip partial selections → (DFVal, Range) +dfVal.departialDcl // strip partial selections → Option[(DFVal.Dcl, Range)] +dfVal.stripPortSel // PortByNameSelect → underlying Dcl, else identity +dfVal.isBubble // contains a don't-care / bubble constant +``` + +--- + +### DFVal.Const + +```scala +final case class DFVal.Const( + dfType: DFType, + data: Any, // actual value; cast to dfType.Data + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +Extends `CanBeExpr`, `CanBeGlobal`. +`data` is typed as `dfType.Data` — e.g. `Option[BigInt]` for `DFUInt`, `(BitVector, BitVector)` for `DFBits`. + +--- + +### DFVal.Dcl + +```scala +final case class DFVal.Dcl( + dfType: DFType, + modifier: DFVal.Modifier, // dir + special + initRefList: List[Dcl.InitRef], + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +// companion +type Dcl.InitRef = DFRef.TwoWay[DFVal, Dcl] +``` + +**`DFVal.Modifier`:** + +```scala +final case class Modifier(dir: Modifier.Dir, special: Modifier.Special) +enum Dir: VAR, IN, OUT, INOUT +enum Special: Ordinary, REG, SHARED + +mod.isPort // IN | OUT | INOUT +mod.isReg // special == REG +mod.isShared // special == SHARED +``` + +**Extension methods on `Dcl`:** + +```scala +dcl.initList // List[DFVal] — resolved init values +dcl.isClkDcl // dfType is DFOpaque(kind = Clk) +dcl.isRstDcl // dfType is DFOpaque(kind = Rst) +dcl.hasNonBubbleInit // initRefList non-empty and first element is not bubble +``` + +--- + +### DFVal.Func + +```scala +final case class DFVal.Func( + dfType: DFType, + op: Func.Op, + args: List[DFVal.Ref], // type DFVal.Ref = DFRef.TwoWay[DFVal, DFMember] + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +Extends `CanBeExpr`, `CanBeGlobal`. + +**`Func.Op` enum** (all operators): +``` ++ - * / === =!= < > <= >= & | ^ % ++ +>> << ** ror rol reverse repeat +unary_- unary_~ unary_! +rising falling +clog2 max min abs sel +InitFile(format, path) +``` + +`Func.Op.associativeSet` = `{+, -, *, &, |, ^, ++, max, min}` + +--- + +### DFVal.Alias subtypes + +All aliases share: +```scala +val relValRef: DFRef.TwoWay[DFVal, Alias] // the value being aliased +``` + +Two sub-traits: +- `Alias.Consumer` — `relValRef: ConsumerRef` — consumes the source entirely (History) +- `Alias.Partial` — `relValRef: PartialRef` — partial view of source; propagates mutability (AsIs, ApplyRange, ApplyIdx, SelectField) + +#### DFVal.Alias.AsIs +```scala +final case class DFVal.Alias.AsIs( + dfType: DFType, // target type (may differ from relVal.dfType for casts) + relValRef: PartialRef, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` +Used for: `.as(T)` type casts, `.actual` on opaques, `IdentTag`-tagged identity aliases. + +Tag-based extractors: +```scala +Ident(underlying) // AsIs tagged IdentTag → underlying DFVal +Bind(underlying) // Alias tagged BindTag → underlying DFVal +OpaqueActual(relVal) // AsIs where relVal.dfType is DFOpaque and alias.dfType == actualType +AsOpaque(relVal) // AsIs where alias.dfType is DFOpaque and relVal.dfType == actualType +``` + +#### DFVal.Alias.History +```scala +final case class DFVal.Alias.History( + dfType: DFType, + relValRef: ConsumerRef, + step: Int, + op: History.Op, // State | Pipe + initRefOption: Option[History.InitRef], + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +enum History.Op: State, Pipe // State = .prev in DF / .reg in RT; Pipe = DF pipe constraint + +history.initOption // Option[DFVal] +history.hasNonBubbleInit // Boolean +``` + +#### DFVal.Alias.ApplyRange +```scala +final case class DFVal.Alias.ApplyRange( + dfType: DFType, + relValRef: PartialRef, + idxHighRef: IntParamRef, // high bit index (inclusive) + idxLowRef: IntParamRef, // low bit index (inclusive) + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +applyRange.elementWidth // width of each element in the range +``` + +#### DFVal.Alias.ApplyIdx +```scala +final case class DFVal.Alias.ApplyIdx( + dfType: DFType, + relValRef: PartialRef, + relIdx: DFVal.Ref, // the index value + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +// Companion extractor for compile-time constant indices: +DFVal.Alias.ApplyIdx.ConstIdx(idx: Int) // unapply on DFVal.Const → Option[Int] +``` + +#### DFVal.Alias.SelectField +```scala +final case class DFVal.Alias.SelectField( + dfType: DFType, + relValRef: PartialRef, + fieldName: String, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +--- + +### DFVal.DesignParam +```scala +final case class DFVal.DesignParam( + dfType: DFType, + defaultValRef: DesignParam.DefaultValRef, // → default value or DFMember.Empty + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +type DesignParam.DefaultValRef = DFRef.TwoWay[DFVal | DFMember.Empty, DesignParam] +``` + +Key accessor methods on `DesignParam`: +- `param.appliedValRefOpt` — `Option[DFDesignBlock.ParamRef]` — `Some` when the owner design has an applied value in its `paramMap`, `None` otherwise +- `param.appliedValOpt` — `Option[DFVal]` — resolved applied value (if any) +- `param.appliedOrDefaultValRef` — `DFVal.Ref` — applied ref if present, else `defaultValRef` +- `param.appliedOrDefaultVal` — `DFVal` — applied value if present, else default value + +--- + +### DFVal.Special +```scala +final case class DFVal.Special( + dfType: DFType, + kind: Special.Kind, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +enum Special.Kind: NOTHING, OPEN, CLK_FREQ +``` + +--- + +### DFVal.PortByNameSelect +```scala +final case class DFVal.PortByNameSelect( + dfType: DFType, + designInstRef: PortByNameSelect.Ref, // → DFDesignInst + portNamePath: String, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +type PortByNameSelect.Ref = DFRef.TwoWay[DFDesignInst, PortByNameSelect] + +portByNameSelect.getPortDcl // resolve to actual DFVal.Dcl (via DB.dupPortsByName) + +// Extractor: +DFVal.PortByNameSelect.Of(dcl) // unapply → Option[DFVal.Dcl] +``` + +--- + +## Statement subtypes + +### DFNet (assignment / connection) + +```scala +final case class DFNet( + lhsRef: DFNet.Ref, // DFRef.TwoWay[DFVal | DFInterfaceOwner, DFNet] + op: DFNet.Op, + rhsRef: DFNet.Ref, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +enum DFNet.Op: Assignment, NBAssignment, Connection, ViaConnection, LazyConnection +``` + +**Extension methods:** +```scala +net.isAssignment // Assignment | NBAssignment +net.isConnection // Connection | ViaConnection | LazyConnection +net.isViaConnection +net.isLazyConnection +``` + +**Pattern extractors** (most useful in stages): +```scala +DFNet.Assignment(toVal, fromVal) // Assignment or NBAssignment; toVal and fromVal are DFVal +DFNet.BAssignment(toVal, fromVal) // blocking only (op == Assignment) +DFNet.NBAssignment(toVal, fromVal) // non-blocking only (op == NBAssignment) +DFNet.Connection(toVal, fromVal, swapped) + // toVal: DFVal.Dcl | DFVal.Special | DFInterfaceOwner + // fromVal: DFVal | DFInterfaceOwner + // swapped: Boolean — true if lhs/rhs were physically reversed +``` + +--- + +### Wait +```scala +final case class Wait( + triggerRef: Wait.TriggerRef, // DFRef.TwoWay[DFVal, Wait] + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +--- + +### TextOut +```scala +final case class TextOut( + op: TextOut.Op, + msgParts: List[String], // literal string segments + msgArgs: List[DFVal.Ref], // interpolated value arguments + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +enum TextOut.Op: + case Print, Println, Debug, Finish + case Report(severity: Severity) + case Assert(assertionRef: AssertionRef, severity: Severity) +enum TextOut.Severity: Info, Warning, Error, Fatal +``` + +--- + +### Goto +```scala +final case class Goto( + stepRef: Goto.Ref, // DFRef.TwoWay[StepBlock | ThisStep | NextStep | FirstStep, Goto] + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +// Special step marker singletons: +case object ThisStep extends DFMember.Empty +case object NextStep extends DFMember.Empty +case object FirstStep extends DFMember.Empty +``` + +--- + +## DFBlock subtypes + +### DFDesignBlock (also aliased as `DFDesignInst`) +```scala +final case class DFDesignBlock( + domainType: DomainType, + dclMeta: Meta, // declaration-site meta (class name, position, …) + instMode: DFDesignBlock.InstMode, + ownerRef: DFOwner.Ref, + meta: Meta, // instance-site meta (val name, position) + tags: DFTags +) +enum InstMode: Normal, Def, Simulation +enum InstMode.BlackBox: NA, Files(path), Library(libName, nameSpace), VendorIP(vendor, typeName) +``` + +**Extension methods:** +```scala +design.isDuplicate // tagged DuplicateTag — has NO members in DB (ports/domains removed) + // Use dupPortsByName/dupDesignDomainBlockMap for synthetic members +design.isBlackBox // instMode is BlackBox +design.isVendorIPBlackbox +design.inSimulation // instMode is Simulation +design.isTop // no ownerRef pointing to another design +design.dclName // design class name (from dclMeta) +design.getCommonDesignWith(other) // nearest common ancestor DFDesignBlock +``` + +**Companion extractors:** +```scala +DFDesignBlock.Top() // matches the top-level design (isTop == true) +``` + +--- + +### DomainBlock +```scala +final case class DomainBlock( + domainType: DomainType, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +--- + +### ProcessBlock +```scala +final case class ProcessBlock( + sensitivity: ProcessBlock.Sensitivity, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +sealed trait ProcessBlock.Sensitivity +case object ProcessBlock.Sensitivity.All // process(all) +final case class ProcessBlock.Sensitivity.List(refs: List[DFVal.Ref]) // process(x, y) +``` + +--- + +### StepBlock +```scala +final case class StepBlock( + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +// Extension methods: +stepBlock.isOnEntry // getName == "onEntry" +stepBlock.isOnExit // getName == "onExit" +stepBlock.isFallThrough // getName == "fallThrough" +stepBlock.isRegular // none of the above +``` + +--- + +### DFConditional + +#### DFMatchHeader +```scala +final case class DFMatchHeader( + dfType: DFType, + selectorRef: DFVal.Ref, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +#### DFCaseBlock +```scala +final case class DFCaseBlock( + pattern: DFCaseBlock.Pattern, + guardRef: Block.GuardRef, // optional boolean guard + prevBlockOrHeaderRef: DFCaseBlock.Ref, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +**`DFCaseBlock.Pattern` hierarchy:** +```scala +sealed trait Pattern +case object CatchAll // case _ +final case class Singleton(valueRef: DFVal.Ref) // case 42 +final case class Alternative(list: List[Pattern]) // case 1 | 2 | 3 +final case class Struct(name: String, fieldPatterns: List[Pattern])// case MyStruct(...) +final case class Bind(ref: Bind.Ref, pattern: Pattern) // case x @ pattern +final case class NamedArg(name: String, pattern: Pattern) // named arg +final case class BindSI(op, parts, refs) // string-interpolation bind +``` + +#### DFIfHeader +```scala +final case class DFIfHeader( + dfType: DFType, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +#### DFIfElseBlock +```scala +final case class DFIfElseBlock( + guardRef: Block.GuardRef, // Some → if/else-if guard; None → else + prevBlockOrHeaderRef: DFIfElseBlock.Ref, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +--- + +### DFLoop + +#### DFForBlock +```scala +final case class DFForBlock( + iteratorRef: DFForBlock.IteratorRef, // DFRef.TwoWay[DFVal.Dcl, DFForBlock] + rangeRef: DFForBlock.RangeRef, // DFRef.TwoWay[DFRange, DFForBlock] + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +#### DFWhileBlock +```scala +final case class DFWhileBlock( + guardRef: DFWhileBlock.GuardRef, // DFRef.TwoWay[DFVal, DFWhileBlock] + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +``` + +#### DFLoop.Block extension methods +```scala +loop.isCombinational // tagged CombinationalTag +loop.isFallThrough // tagged FallThroughTag +``` + +--- + +### DFRange +```scala +final case class DFRange( + startRef: DFRange.Ref, // DFRef.TwoWay[DFVal, DFRange] + endRef: DFRange.Ref, + op: DFRange.Op, + stepRef: DFRange.Ref, + ownerRef: DFOwner.Ref, + meta: Meta, + tags: DFTags +) +enum DFRange.Op: Until, To +``` + +--- + +## DFType hierarchy + +``` +DFType (sealed) +├── DFBoolOrBit (sealed) +│ ├── DFBool — boolean, width = 1 +│ └── DFBit — single hardware bit, width = 1 +├── DFBits(widthParamRef) — bit vector +├── DFDecimal(signed, widthParamRef, fractionWidth, nativeType) +│ ├── DFUInt(w) — unsigned integer +│ ├── DFSInt(w) — signed integer +│ └── DFInt32 — standard 32-bit signed (nativeType = Int32) +├── DFEnum(name, widthParam, entries: ListMap[String, BigInt]) +├── DFDouble — 64-bit floating point +├── DFString — unbounded string +├── DFVector(cellType, cellDimParamRefs) — ordered collection +├── DFStruct(name, fieldMap: ListMap[String, DFType]) +├── DFOpaque(name, kind, id, actualType) +├── DFUnit — unit / void +├── DFNothing — bottom type +├── DFTime — physical time quantity +├── DFFreq — physical frequency quantity +└── DFNumber — dimensionless literal number +``` + +**Type marker traits:** +- `ComposedDFType` — has inner types (Vector, Struct, Opaque) +- `NamedDFType` — has a `name: String` (Struct, Opaque, Enum) + +**Common DFType API:** +```scala +dfType.width // Int (requires MemberGetSet for parameterised widths) +dfType.getRefs // List[DFRef.TypeRef] +dfType.copyWithNewRefs // using RefGen +dfType.isSimilarTo(that) // structural equivalence ignoring identity +``` + +**DFVector helpers:** +```scala +vector.length // Int +vector.cellType // DFType of each element +``` + +**DFStruct helpers:** +```scala +struct.fieldMap // ListMap[String, DFType] (ordered) +struct.fieldIndex(name) // Int — field index +struct.fieldRelBitLow(name) // Int — bit offset of field within struct +``` + +**DFOpaque.Kind:** +```scala +sealed trait Kind +case object General extends Kind +sealed trait Magnet extends Kind +case object Clk extends Magnet +case object Rst extends Magnet +case object Magnet extends Magnet // generic magnet + +opaque.isMagnet // kind.isInstanceOf[Magnet] +``` + +**`IntParamRef`** — used for widths and indices in DFBits, DFDecimal, DFVector, ApplyRange: +```scala +opaque type IntParamRef = DFRef.TypeRef | Int +paramRef.getInt // resolve to Int (using MemberGetSet) +paramRef.isInt // true if literal +paramRef.isRef // true if parameterised +``` + +--- + +## DomainType + +```scala +enum DomainType: + case DF // dataflow (timing-agnostic) + case RT(cfg: RTDomainCfg) // register-transfer + case ED // event-driven (Verilog/VHDL semantics) +``` + +### RTDomainCfg +```scala +enum RTDomainCfg: + case Derived // inherit from context + case Related(relatedDomainRef: RTDomainCfg.RelatedDomainRef) + case Explicit(name: String, clkCfg: ClkCfg, rstCfg: RstCfg) +``` + +### ClkCfg.Explicit +```scala +final case class ClkCfg.Explicit( + edge: ClkCfg.Edge, // Rising | Falling + rate: RateNumber, + portName: String, + inclusionPolicy: ClkRstInclusionPolicy +) +enum ClkCfg.Edge: Rising, Falling +``` + +### RstCfg.Explicit +```scala +final case class RstCfg.Explicit( + mode: RstCfg.Mode, // Async | Sync + active: RstCfg.Active, // Low | High + portName: String, + inclusionPolicy: ClkRstInclusionPolicy +) +enum RstCfg.Mode: Async, Sync +enum RstCfg.Active: Low, High +enum ClkRstInclusionPolicy: AsNeeded, AlwaysAtTop +``` + +--- + +## Meta + +```scala +final case class Meta( + nameOpt: Option[String], + position: Position, + docOpt: Option[String], + annotations: List[HWAnnotation] +) +meta.isAnonymous // nameOpt.isEmpty +meta.name // generated name if anonymous, else nameOpt.get +meta.anonymize() // copy with nameOpt = None +meta.setName(name) +``` + +--- + +## DFTags + +```scala +opaque type DFTags = Map[String, DFTag] +tags.tag[CT <: DFTag](t) // add tag, returns new DFTags +tags.removeTagOf[CT] // remove tag by type +tags.getTagOf[CT] // Option[CT] +tags.hasTagOf[CT] // Boolean +tags.++(that) // merge two DFTags +DFTags.empty +``` + +**Built-in tags:** +```scala +case object DuplicateTag // duplicate design instance — NO members in DB; use dupPortsByName +case object IteratorTag // Dcl is a for-loop iterator variable +case object IdentTag // Alias.AsIs is a pure identity (named alias of itself) +case object BindTag // Alias is a pattern-match bind variable +case object CombinationalTag // loop/block is combinational (no cycles) +case object FallThroughTag // loop/block falls through to next step +case class DefaultRTDomainCfgTag(cfg: RTDomainCfg.Explicit) +case object ExtendTag +case object TruncateTag +case class DFHDLVersionTag(version: String) +``` + +--- + +## Reference types + +```scala +sealed trait DFRef[+M <: DFMember]: + val grpId: (Int, Int) // group (position hash, counter) + val id: Int + def get(using MemberGetSet): M + def getOption(using MemberGetSet): Option[M] + def copyAsNewRef(using RefGen): this.type + +type DFRefAny = DFRef[DFMember] +``` + +**Subtypes:** +- `DFRef.OneWay[M]` — unidirectional (e.g. `ownerRef`) +- `DFRef.TwoWay[M, O]` — bidirectional; `O` is the member that owns this ref (enables reverse lookup) +- `DFRef.TypeRef` — used for `IntParamRef` (width/index parameters) +- `DFRef.DuplicationRef(owner: DFOwnerNamed)` — special `OneWay` ref for analysis-only members (not in `refTable` or `members`). Overrides `get` to return `owner` directly, bypassing `MemberGetSet` lookup. Used by `dupPortsByName` and `dupDesignDomainBlockMap` to create synthetic port Dcls and domain blocks for duplicate designs. + +**Pattern extractor** (very common in stages): +```scala +DFRef(member) // matches any DFRef and extracts the resolved member +// Example: +case DFNet(DFRef(lhs: DFVal), _, DFRef(rhs: DFVal), _, _, _) => ... +``` + +### RefGen + +```scala +class RefGen private (magnetID, grpId, lastId): + def genOneWay[M](): DFRef.OneWay[M] + def genTwoWay[M, O](): DFRef.TwoWay[M, O] + def genTypeRef(): DFRef.TypeRef + def getGrpId: (Int, Int) + def setGrpId(id: (Int, Int)): Unit + +RefGen.initial // fresh RefGen (grpId = (0,0)) +RefGen.fromGetSet // RefGen seeded from the current DB's existing IDs +``` + +--- + +## DB — the design database + +```scala +final case class DB( + members: List[DFMember], // ordered flat list (top-design first) + refTable: Map[DFRefAny, DFMember], + globalTags: DFTags, + srcFiles: List[SourceFile] +) +``` + +**Key computed properties:** +```scala +db.top // DFDesignBlock — first member, always the root design +db.topIOs // List[DFVal.Dcl] — ports of the top design +db.getSet // MemberGetSet (immutable) +db.memberTable // Map[DFMember, Set[DFRefAny]] — member → refs pointing to it +db.originRefTable // Map[DFRef.TwoWayAny, DFMember] — ref → member that owns it +db.originMemberTable // Map[DFMember, Set[DFMember]] — member → members referencing it +db.ownerMemberList // List[(DFOwner, List[DFMember])] — members grouped by owner +db.membersNoGlobals // members excluding global values +db.membersGlobals // global CanBeGlobal values only +db.inSimulation // top has no ports (simulation context) +db.inBuild // top has a device constraint tag +``` + +**Design duplication properties:** + +Duplicate designs (tagged `DuplicateTag`) have **no members** in the DB — their ports, domain blocks, and other members are removed during immutable DB creation. Instead, synthetic members with `DuplicationRef` owners are created on-demand: + +```scala +db.dupDesignToOrigMap // Map[DFDesignBlock, DFDesignBlock] — dup design → origin design +db.dupPortsByName // Map[DFDesignInst, ListMap[String, DFVal.Dcl]] — design → named ports + // includes both real ports (for origins) and DuplicationRef-backed + // synthetic ports (for duplicates, with PBNS dfType overrides) +db.dupDesignDomainBlockMap // Map[(DFDesignBlock, DomainBlock), DomainBlock] + // maps (dupDesign, origDomainBlock) → dupDomainBlock +db.dupDomainOwnerPublicMemberList // List[(DFDomainOwner, List[DFMember])] + // domain owners with their public members (ports, designs, domains), + // including dup-copy entries for duplicate designs +db.dupDomainOwnerPublicMemberTable // Map form of the above +``` + +**Important:** `dupPortsByName` is the primary port lookup — use it instead of iterating `members` for ports. It handles duplicate designs transparently. `getPortDcl` on `PortByNameSelect` uses it internally. + +**Patching:** +```scala +db.patch(patches: List[(DFMember, Patch)]): DB +``` + +### MemberGetSet + +```scala +trait MemberGetSet: + val isMutable: Boolean + def designDB: DB + def apply[M <: DFMember, M0 <: M](ref: DFRef[M]): M0 // resolve ref → member + def getOption[M <: DFMember, M0 <: M](ref: DFRef[M]): Option[M0] + def getOrigin(ref: DFRef.TwoWayAny): DFMember // reverse lookup + def set[M <: DFMember](orig: M)(f: M => M): M // update member + def replace[M <: DFMember](orig: M)(updated: M): M + def remove[M <: DFMember](member: M): M +``` + +`db.getSet` is immutable (`isMutable = false`). In stages, it is passed as `given` automatically: +```scala +def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = ... +``` + +--- + +## Analysis extractors (dfhdl.compiler.analysis.*) + +Import with `import dfhdl.compiler.analysis.*` (already in scope via the standard stage imports). +All require `using MemberGetSet`. + +### DFVal extractors + +| Extractor | Matches | Extracts | +|---|---|---| +| `DclVar()` | `DFVal.Dcl` with `modifier.dir == VAR` | Boolean (use in condition) | +| `DclConst()` | Any `DFVal.CanBeExpr` that is named and constant | Boolean | +| `DclPort()` | `DFVal.Dcl` with IN/OUT/INOUT | Boolean | +| `DclIn()` | `DFVal.Dcl` with IN | Boolean | +| `DclOut()` | `DFVal.Dcl` with OUT | Boolean | +| `IteratorDcl()` | `DFVal.Dcl` tagged `IteratorTag` | Boolean | +| `Ident(underlying)` | `DFVal.Alias.AsIs` tagged `IdentTag` | `DFVal` | +| `Bind(underlying)` | `DFVal.Alias` tagged `BindTag` | `DFVal` | +| `OpaqueActual(relVal)` | `AsIs` unwrapping an opaque | `DFVal` | +| `AsOpaque(relVal)` | `AsIs` casting to an opaque | `DFVal` | +| `ClkEdge(sig, edge)` | `DFVal.Func` with `rising`/`falling` op | `(DFVal, ClkCfg.Edge)` | +| `RstActive(sig, active)` | Reset condition expression | `(DFVal, RstCfg.Active)` | +| `BlockRamVar()` | `DFVal.Dcl` VAR of DFVector with only index accesses | Boolean | +| `DefaultOfDesignParam(dp)` | A value used as default of a DesignParam | `DFVal.DesignParam` | + +### DFVal extension methods (analysis) + +```scala +dfVal.getReadDeps // Set[DFValReadDep] — things that read this value +dfVal.getPartialAliases // Set[DFVal.Alias.Partial] — aliases of this value +dfVal.getConnectionTo // Option[DFNet] — single connection driving this value +dfVal.getConnectionsFrom // Set[DFNet] — connections driven from this value +dfVal.getAssignmentsTo // Set[DFVal] — values assigned to this +dfVal.getAssignmentsFrom // Set[DFVal] — values assigned from this +dfVal.getPortsByNameSelectors // List[DFVal.PortByNameSelect] (ports only) +dfVal.isReferencedByAnyDclOrDesign // Boolean — true if referenced by a Dcl, DclConst, or DFDesignBlock +dfVal.isConstVAR // VAR never assigned/connected +dfVal.isAllowedMultipleReferences // Boolean +dfVal.isPartialNetDest // Boolean — is a partial assignment/connection target +dfVal.flatName // String — name derived from structure if anonymous +dfVal.suggestName // Option[String] — inferred name from context +dfVal.collectRelMembers(includeOrig) // List[DFVal] — this + all anonymous arg deps +``` + +**Reverse member lookup (any DFMember):** +```scala +member.originMembers // Set[DFMember] — members whose refs point to this +member.originMembersNoTypeRef // Set[DFMember] — same, excluding TypeRef references +member.consumesCycles // Boolean — true for Wait, StepBlock, Goto, non-comb loops +``` + +### ComposedDFTypeReplacement + +For recursive type transformation across Struct/Vector/Opaque nesting: + +```scala +class ComposedDFTypeReplacement[H]( + preCheck: DFType => Option[H], + updateFunc: PartialFunction[(DFType, H), DFType] +)(using MemberGetSet): + def unapply(dfType: DFType): Option[DFType] +``` + +- `preCheck` — return `Some(helper)` to trigger replacement at this node; `None` to skip (but still recurse into composed children) +- `updateFunc` — given `(composedOrOriginal DFType, helper)` → produce the replacement type +- Recurses automatically into `DFStruct.fieldMap`, `DFVector.cellType`, `DFOpaque.actualType` + +```scala +// Example: replace all DFOpaque types with their actualType +object DropOpaques extends ComposedDFTypeReplacement( + preCheck = { case dt: DFOpaque => Some(()); case _ => None }, + updateFunc = { case (dt: DFOpaque, _) => dt.actualType } +) +// Usage in pattern match: +case dfVal @ ComposedOpaqueDFValReplacement(updatedDFVal) => + dfVal -> Patch.Replace(updatedDFVal, Patch.Replace.Config.FullReplacement) +``` diff --git a/.claude/commands/new-stage.md b/.claude/commands/new-stage.md new file mode 100644 index 000000000..16986a152 --- /dev/null +++ b/.claude/commands/new-stage.md @@ -0,0 +1,1047 @@ +# DFHDL Compiler Stage Creation Guide + +> **For contributors adding or modifying compiler transformation stages in DFHDL.** +> This skill is version-controlled alongside the codebase — keep it updated when stage infrastructure changes. +> After completing a stage, **update this file** with any general lessons learned (new patterns, API +> behaviours, pitfalls). See the "Keeping This Skill Up to Date" section at the bottom for guidance. + +You are helping create or modify a DFHDL compiler transformation stage. Use the complete reference below to produce correct, idiomatic code. + +--- + +## Architecture Overview + +A **stage** is a single transformation pass over the design IR (`DB`). Stages are chained by the `StageRunner`, which resolves `dependencies` recursively and re-runs any `nullified` stages when needed. + +``` +Design (Scala frontend) → DB → Stage1 → Stage2 → ... → StagedN → backend printer +``` + +The `DB` is an **immutable snapshot**: each stage receives the current `DB`, computes a patch list, and returns a new `DB`. + +--- + +## Required Stage Properties + +Every stage **must** satisfy two invariants. Violating either causes subtle, hard-to-debug compiler bugs. + +### 1. Determinism — same input → same output, every time + +Given the same input `DB`, a stage must always produce bit-for-bit the same output `DB`, regardless of how many times the overall compilation is run. + +**Common causes of non-determinism to avoid:** +- Iterating over `Set`, `Map`, or any unordered collection to build the patch list — iteration order is not guaranteed. Always convert to a sorted or ordered structure first, or derive order from `designDB.members` (which is a `List` and is ordered). +- Using `hashCode`-based identity anywhere in the transformation logic. +- Relying on mutable external state (counters, caches, `var`s outside the `transform` call). + +**Rule:** Build patch lists by iterating `designDB.members` (ordered) or `designDB.blockMemberList` (ordered). Never collect from a `Set` or `HashMap` directly. + +### 2. Idempotency — `f(f(x)) == f(x)` + +Applying a stage to its own output must yield the same output again. In other words, if the transformation function is `f`, then for every input DB `x`: + +``` +f(f(x)) == f(x) +``` + +This is a **design guideline**, not something that needs to be formally proved. The intent is that a stage should recognize when the IR is already in the form it produces and leave it unchanged. A stage that keeps mutating an already-transformed DB is a sign its pattern matches are too broad. + +**How to achieve idempotency in practice:** +- **Match on the source form only.** Pattern-match on the exact IR shape that the stage is responsible for transforming. The transformed shape should not match the same pattern, so a second run produces an empty patch list. +- **Structural self-consistency.** After `f(x)` is applied, the resulting DB should contain no members that satisfy the stage's match predicates. + +--- + +## Core Infrastructure + +### `Stage` trait +```scala +// compiler/stages/src/main/scala/dfhdl/compiler/stages/Stage.scala +trait Stage extends Product, Serializable, HasTypeName derives CanEqual: + final lazy val depSet: Set[Stage] = dependencies.toSet + def dependencies: List[Stage] // prerequisite stages, run first + def nullifies: Set[Stage] // stages that must re-run after this one + def runCondition(using CompilerOptions): Boolean = true // skip stage when false + def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB // the transformation +``` + +### Special stage subtypes +```scala +sealed trait SpecialControlStage extends Stage // skips sanity checks in trace logging + +trait NoCheckStage extends SpecialControlStage // use when stage intentionally leaves dangling anon refs + +abstract class BundleStage(deps: Stage*) extends NoCheckStage: // no-op, used purely for dependency ordering + def transform(db: DB)(...): DB = db +``` + +### `HasDB` — accepts DB, Design, StagedDesign, or CompiledDesign as input +```scala +trait HasDB[T]: + def apply(t: T): DB +extension [T: HasDB](t: T) def db: DB = summon[HasDB[T]](t) +``` + +### `StageRunner` — recursive dependency-aware executor +```scala +object StageRunner: + def run(stage: Stage)(designDB: DB)(using CompilerOptions, PrinterOptions): DB +``` +- Traverses `dependencies` depth-first before running a stage. +- Removes `nullified` stages from the "done" set so they re-run if needed later. +- Automatically runs `sanityCheck` after each non-`NoCheckStage`. +- At `TRACE` log level, prints the code string after each stage that changed the DB. + +--- + +## IR Data Model + +### `DB` — the design database (immutable) +```scala +// compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +case class DB( + members: List[DFMember], // ordered flat list of all IR members + refTable: Map[DFRefAny, DFMember], // reference → member resolution + globalTags: Map[...], + srcFiles: List[...], +): + def getSet: MemberGetSet // member-lookup context (needed as `given`) + def top: DFDesignBlock // root design block + def memberTable: Map[DFMember, ...] // reverse lookup + def patch(patches: List[(DFMember, Patch)]): DB +``` + +### `DFMember` hierarchy +``` +DFMember (sealed) +├── DFVal ─ any value in the design +│ ├── DFVal.Dcl ─ port/var/const declaration +│ ├── DFVal.Func ─ operation / function call +│ ├── DFVal.Alias ─ alias / cast / selection +│ │ ├── DFVal.Alias.AsIs ─ .as(T) cast +│ │ ├── DFVal.Alias.ApplyIdx ─ vector indexing +│ │ └── DFVal.Alias.SelectField ─ struct field +│ └── DFVal.Const ─ literal constant +├── Statement ─ assignment, connection, net +├── DFBlock ─ container for members +│ ├── DFDesignBlock ─ module / design definition +│ ├── ProcessBlock ─ always/process block +│ ├── StepBlock ─ RT step (FSM state) +│ └── DFConditional.Block ─ if/match/while clause +├── DFConditional.Header ─ if/match/while header +├── DFInterfaceOwner +└── DFRange +``` + +### Useful extractor patterns (already in scope via `dfhdl.compiler.analysis.*`) +```scala +DclVar() // matches DFVal.Dcl that is a variable +DclConst() // matches DFVal.Dcl that is a constant +IteratorDcl() // matches for-loop iterator declarations +DclBind() // matches pattern-match bind declarations +Ident(underlying) // matches named alias that just wraps another value +``` + +### Navigation helpers on `DFMember` +```scala +m.getOwner // immediate parent member +m.getOwnerBlock // nearest enclosing block +m.getOwnerDesign // nearest enclosing design block +m.getOwnerDomain // nearest domain-bearing block +m.isAnonymous // true if the value has no user-visible name +m.originMembers // members that reference this member +``` + +### `MemberView.Folded` vs `MemberView.Flattened` + +```scala +owner.members(MemberView.Folded) // direct children only: m.getOwner == owner +owner.members(MemberView.Flattened) // all descendants recursively +``` + +Use `Folded` when processing only immediate children (e.g. iterating steps at one nesting level). +Use `Flattened` when you need to inspect or collect all descendants (e.g. finding all `Goto` +members anywhere inside a `ProcessBlock`, or collecting all members to move with a block). + +A member's **direct owner** is the block for which `m.getOwner == block`. `Flattened` includes +members owned by nested blocks too, which can be confusing: a `Goto` inside a `StepBlock` inside +a `ProcessBlock` appears in `pb.members(Flattened)` but NOT in `pb.members(Folded)`. + +### Domain checks +```scala +m.isInDFDomain // dataflow domain +m.isInRTDomain // register-transfer domain +m.isInProcess // inside a process block +``` + +--- + +## Patch System + +All mutations are expressed as a **patch list**: `List[(DFMember, Patch)]`. Apply them at the end: + +```scala +designDB.patch(patchList) +``` + +### Patch types + +| Patch | Effect | +|---|---| +| `Patch.Replace(newMember, cfg)` | Replace or re-reference a member | +| `Patch.Move(member, cfg)` | Move a member to a different position | +| `Patch.Add(dsn, cfg)` | Insert new members constructed via `MetaDesign` | +| `Patch.Remove(isMoved)` | Remove a member; `isMoved=true` preserves references | +| `Patch.ChangeRef(from, to)` | Redirect a single reference | + +### `Patch.Replace` configs + +```scala +Patch.Replace.Config.FullReplacement // replaces the member in the member list +Patch.Replace.Config.ChangeRefOnly // only updates references, keeps original in list +Patch.Replace.Config.ChangeRefAndRemove // updates refs AND removes original from list +``` + +Optionally scope reference changes with a `RefFilter`: +```scala +Patch.Replace.RefFilter.OfMembers(memberSet) // only refs from specific members +// or implement custom RefFilter: +object MyFilter extends Patch.Replace.RefFilter: + def apply(refs: Set[DFRefAny])(using MemberGetSet): Set[DFRefAny] = refs.filter(...) +``` + +### `Patch.Move` configs +```scala +Patch.Move.Config.Before // insert before the anchor member +Patch.Move.Config.After +Patch.Move.Config.InsideFirst // insert as first child of a block +Patch.Move.Config.InsideLast +``` + +### `Patch.Move` semantics — critical details + +**Descendants are NOT moved automatically.** `Patch.Move(List(member), origOwner, cfg)` only +moves the listed members. If `member` is a `DFOwner` (e.g. a `StepBlock`), its children remain +at their original positions in the flat member list. Since the flat member list must be a valid +pre-order DFS traversal (every member's owner must appear before it), moving a block without its +children violates the ownership invariant and causes `sanityCheck` to fail. + +**Always include descendants when moving a block:** +```scala +val allMembersToMove = block :: block.members(MemberView.Flattened) +anchor -> Patch.Move(allMembersToMove, block.getOwner, Patch.Move.Config.After) +``` + +**Anchor redirect for `DFOwner` + `Config.After`.** When the anchor is a `DFOwner` and config +is `After`, the patch system redirects the physical insertion point to +`anchor.getVeryLastMember` (deepest recursive last descendant). However, the ownership update +uses the **original anchor** to determine the new owner: `newOwner = origAnchor.getOwnerBlock`. +This means after the move, the moved members' `ownerRef`s point to `anchor.getOwnerBlock`, +not to `anchor` itself. + +**Multiple Move patches on the same anchor + config are concatenated** in patchList order. +If you add several `anchor -> Patch.Move(...)` entries for the same `(anchor, Config.After)`, +all the member lists are merged and inserted together after the anchor (or its redirect target). + +**Conflict: a member cannot appear in two patches simultaneously.** If `Patch.Move` lists a +member AND that same member appears in a separate `Patch.Remove` (or another `Patch.Move`), +the DB throws `IllegalArgumentException: Received two different patches for the same member`. +Note that `Patch.Move` internally generates `Patch.Remove` for each listed member. Common +triggers: +- Using a `Goto` as a `Move.Before` anchor (so the Move internally removes it on re-insertion) + while the same `Goto` is also in another Move's members list. +- Moving a parent block with all descendants AND separately moving one of those descendants. + +The fix is always **multi-phase patching**: split conflicting patches into sequential +`db.patch()` calls so each phase operates on a clean, conflict-free DB. + +### `Patch.Add` via `MetaDesign` +Use `MetaDesign` when you need to construct new IR members using the DFHDL frontend DSL: + +```scala +val dsn = new MetaDesign( + anchorMember, // where to insert + Patch.Add.Config.Before // or After, InsideFirst, InsideLast + // or ReplaceWithFirst/ReplaceWithLast(replCfg) +): + // write DFHDL frontend code here — ports, vars, assignments, etc. + val newVar = someType.<>(VAR) + newVar := existingVal.asValAny + +dsn.patch // returns a single (DFMember, Patch) entry for the patch list +``` + +`ReplaceWithLast` / `ReplaceWithFirst` also replace the anchor with the last/first generated member: +```scala +Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.ChangeRefAndRemove) +``` + +See the **MetaDesign Deep Dive** section below for the full mechanics. + +--- + +## DFC — The Implicit Design Context + +`DFC` (`dfhdl.core.DFC`) is the implicit context that flows through every frontend DSL operation. It carries the state needed to register new IR members in the right place during elaboration. + +```scala +// core/src/main/scala/dfhdl/core/DFC.scala +final case class DFC( + nameOpt: Option[String], // name the next member will receive + position: Position, // source-file position for meta/errors + docOpt: Option[String], // doc-comment + annotations: List[HWAnnotation], // active hardware annotations + mutableDB: MutableDB, // the live database being built + refGen: ir.RefGen, // reference-ID generator + tags: ir.DFTags, // member-level tags + elaborationOptionsContr: () => ElaborationOptions +) extends MetaContext +``` + +Every `Design` class provides its own `DFC` as a `given`: + +```scala +trait HasDFC: + lazy val dfc: DFC + protected given DFC = dfc // makes dfc available implicitly in the design body +``` + +All DSL operations (declaring ports, calling `:=`, etc.) require `using DFC`. The compiler plugin +injects DFC into the right places automatically — you rarely summon it explicitly. + +### Key DFC methods used in stages + +```scala +dfc.setMeta(meta) // copy DFC with updated name/position/doc/annotations +dfc.setName("foo") // copy DFC with a specific name for the next member +dfc.anonymize // copy DFC with nameOpt = None (anonymous member) +dfc.setMeta(m.meta) // copy DFC mirroring another member's metadata +dfc.enterOwner(owner) // push a new owner onto the ownership stack +dfc.exitOwner() // pop the owner stack +dfc.owner // current owner (top of stack) +dfc.ownerOrEmptyRef // ir.DFOwner.Ref for the current owner — use this inside MetaDesign + // when constructing raw IR members (avoids `import dfhdl.core.*` ref conflicts) +dfc.inMetaProgramming // true when inside a MetaDesign +dfc.mutableDB // access the MutableDB directly +dfc.getSet // implicit MemberGetSet backed by mutableDB +``` + +### Constructing raw IR members inside `MetaDesign` + +When you need to build an IR member directly (rather than through the DSL) inside a MetaDesign +body — e.g. creating a `Goto` that references an existing `StepBlock` — use this idiom: + +```scala +val dsn = new MetaDesign(anchorMember, Patch.Add.Config.Before): + import dfhdl.core.* // brings refTW, addMember, and IR type helpers into scope + ir.Goto(existingStep.refTW[ir.Goto], dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags).addMember +``` + +Two important details: +- `import dfhdl.core.*` (not `import dfhdl.compiler.ir.*`) is needed inside MetaDesign for + `refTW` and `addMember`. Do **not** write `ir.Goto` with a qualified prefix inside the body + when `dfhdl.compiler.ir.*` is already imported at the file level — use the unqualified `Goto` + or the explicit `ir.Goto` form consistently, but be aware that `import dfhdl.core.*` re-exports + what you need. In practice: inside MetaDesign, use `import dfhdl.core.*` and unqualified names. +- Use `dfc.ownerOrEmptyRef` (a direct method on `DFC`) rather than `dfc.owner.ref`. The latter + requires extension methods that may conflict with `import dfhdl.core.*`. + +### DFCG — the "global" variant + +`DFCG` is an opaque subtype of `DFC`. It is auto-synthesised when no explicit `DFC` is in scope +(i.e., in global/top-level Scala scope outside any design body). Operations that only need a name +and position (e.g., `==` for constant comparison) accept `using DFCG` instead of `using DFC`. + +--- + +## MutableDB vs DB + +### DB — immutable snapshot + +```scala +// compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +final case class DB( + members: List[DFMember], + refTable: Map[DFRefAny, DFMember], + globalTags: DFTags, + srcFiles: List[SourceFile] +) +``` + +- A **frozen, serialisable** record of the whole design. +- The `MemberGetSet` derived from it is **read-only** (`isMutable = false`). +- Every stage receives a `DB` and returns a new `DB`; it never mutates the one it received. +- Used by: stage `transform` methods, printers, analysis passes. + +### MutableDB — live workspace during elaboration + +```scala +// core/src/main/scala/dfhdl/core/MutableDB.scala +final class MutableDB() +``` + +- A **mutable** collection of `MemberEntry` records, reference tables, and ownership state. +- Lives inside `DFC`; every frontend DSL call that registers a member touches it. +- The `MemberGetSet` derived from it is **read-write** (`isMutable = true`). +- Converted to an immutable `DB` snapshot via `.immutable` (called internally by `design.getDB`). +- Used by: `Design` subclasses during Scala elaboration, `MetaDesign` during stage patching. + +| | `DB` | `MutableDB` | +|---|---|---| +| Mutability | Immutable case class | Mutable class | +| Phase | Post-elaboration (compilation) | During elaboration | +| `getSet.isMutable` | `false` | `true` | +| Member changes | None | `addMember`, `plantMember`, `newRefFor` | +| Ownership context | Static | OwnershipContext stack (enter/exit) | +| Serialisable | Yes (upickle) | No | +| Conversion | — | `.immutable` → `DB` | + +### Key MutableDB operations (used inside `MetaDesign`) + +```scala +mutableDB.addMember(member) // register a member under the current owner +mutableDB.plantMember(owner, member) // register member, forcing a specific owner +mutableDB.newRefFor(ref, member) // update what an existing reference points to +mutableDB.injectMetaGetSet(getSet) // allow meta-design members to reference the parent DB +mutableDB.OwnershipContext.enter(owner) // push owner +mutableDB.OwnershipContext.exit() // pop owner +mutableDB.immutable // snapshot → DB +``` + +--- + +## MetaDesign — Deep Dive + +`MetaDesign` (`core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala`) is the bridge +between the frontend DSL and stage patching. It lets you write ordinary DFHDL code (ports, vars, +assignments) inside a stage and have those members injected into the DB at a precise location. + +### Signature + +```scala +abstract class MetaDesign[+D <: DomainType]( + positionMember: ir.DFMember, // anchor: where to inject relative to + addCfg: Patch.Add.Config, + domainType: D = DomainType.DF +)(using + getSet: ir.MemberGetSet, // parent DB access (injected automatically by the stage) + refGen: ir.RefGen // shared reference generator +) extends Design with reflect.Selectable +``` + +`MetaDesign` extends `Design`, so you can write any DFHDL DSL code in its body. It is **ephemeral** +— the MetaDesign block itself is never present in the final DB; only the members created inside it +survive. + +### How the DFC is set up + +```scala +final override protected def __dfc: DFC = + DFC.emptyNoEO.copy(refGen = refGen, position = positionMember.meta.position) +``` + +The MetaDesign starts with a minimal DFC that: +- Reuses the **same `refGen`** as the calling stage (ensures generated reference IDs are globally + unique and don't clash with existing members). +- Copies the **source position** of the anchor member (so synthesised members appear to originate + at the right place in the source). + +After construction, the MetaDesign calls: +```scala +dfc.mutableDB.injectMetaGetSet(getSet) // lets members inside reference the parent DB +``` +This is what makes it possible to reference existing IR members (e.g., `existingVal.asValAny`) +from within a MetaDesign body. + +### Where members are injected + +```scala +lazy val injectedOwner: ir.DFOwner = addCfg match + case InsideFirst | InsideLast => + positionMember.asInstanceOf[ir.DFOwner] // positionMember must be a block + case _ if globalInjection => getSet.designDB.top + case _ => positionMember.getOwner // same owner as anchor +``` + +For `Before` / `After` / `ReplaceWith*`, members are created inside the **same owner as the anchor +member** (a sibling). For `InsideFirst` / `InsideLast`, members are created **inside** the anchor +(the anchor must be a block/design). + +### The `.patch` property + +```scala +lazy val patch = positionMember -> Patch.Add(this, addCfg) +``` + +This is a `(DFMember, Patch)` pair ready to be added to a stage's patch list. When the DB applies +this patch it: +1. Extracts the members created inside the MetaDesign. +2. Removes the MetaDesign block itself. +3. Inserts the members at the correct position relative to `positionMember` according to `addCfg`. + +### Planting existing IR members + +Sometimes you want to move or clone members that **already exist in the DB** into the MetaDesign +context: + +```scala +// Inject a single existing IR member, forcing it under the current owner +plantMember(existingIRMember) + +// Inject a set of members, re-owning those that belonged to baseOwner +plantMembers(baseOwner, memberIterable) + +// Deep-clone a list of members (new refs), re-mapping internal references +val cloneMap = plantClonedMembers(baseOwner, memberList) +// cloneMap: Map[original -> clone] for further reference remapping +``` + +### Temporarily switching owner during construction + +```scala +applyBlock(someOwner): + // members created here belong to someOwner, not the current owner + val v = SomeType <> VAR +``` + +### Converting IR members to core types inside MetaDesign + +Because MetaDesign extends Design, the following exports are always available inside its body: + +```scala +existingIRVal.asValAny // ir.DFVal → core DFValAny (wrap without adding to DB) +existingCoreVal.asIR // core DFVal → ir.DFVal (unwrap) +plantMember(irMember) // add an existing IR member to this MetaDesign's DB +``` + +--- + +## IR Layer vs Core Layer + +DFHDL uses a strict two-layer architecture: + +| | IR layer (`dfhdl.compiler.ir`) | Core layer (`dfhdl.core`) | +|---|---|---| +| Purpose | Immutable AST, serialisable | Live DSL objects during elaboration | +| DFVal | `ir.DFVal` — sealed trait + case-class subtypes | `core.DFVal[T, M]` — opaque wrapper around `ir.DFVal` | +| DFType | `ir.DFType` — sealed ADT (DFBits, DFStruct, …) | Extension methods on `ir.DFType` via `.asFE[T]` | +| Design block | `ir.DFDesignBlock` — immutable case class | `core.Design` — live abstract class with DFC | +| Mutability | Immutable; updates return new instances (`.copy`) | Mutable via `MutableDB` | +| Lifetime | Persists in `DB` across all stages | Ephemeral; discarded after `design.getDB` | +| Serialisable | Yes | No | + +### `ir.DFVal` — the IR representation + +```scala +// compiler/ir — sealed trait with case-class subtypes +sealed trait DFVal extends DFMember.Named: + val dfType: DFType +// Subtypes: +// DFVal.Const — literal constant with data +// DFVal.Dcl — port / var / const declaration +// DFVal.Func — computed expression / operator +// DFVal.Alias.AsIs — type cast (.as(T)) +// DFVal.Alias.ApplyIdx — vector indexing +// DFVal.Alias.SelectField — struct field selection +// DFVal.Alias.History — .prev(n) +// DFVal.Alias.ApplyRange — bit-range slice +// DFVal.DesignParam — design parameter reference +// DFVal.Special — NOTHING, OPEN, CLK_FREQ +``` + +Every IR member is a **pure data record** — a case class with no behaviour beyond `.copy()`. +When a stage needs to change a member, it creates a modified copy and patches it in. + +### `core.DFVal[T, M]` — the core (frontend) representation + +```scala +// core/src/main/scala/dfhdl/core/DFVal.scala +into final class DFVal[+T <: DFTypeAny, +M <: ModifierAny](val irValue: ir.DFVal | DFError) + extends DFMember[ir.DFVal] with Selectable +``` + +- An **opaque value class** — zero runtime overhead, just wraps `ir.DFVal`. +- `T` is the DFHDL type (e.g., `DFBits[8]`); `M` is the modifier (IN, OUT, VAR, CONST, …). +- All DSL operations (arithmetic, assignments, `:=`, `<>`) are extension methods on `DFVal`. +- Requires `using DFC` to register the resulting IR members in the `MutableDB`. +- The `| DFError` union allows deferred error reporting without exceptions. + +### Key conversion methods + +```scala +// IR → Core (wrapping) +irDFVal.asValAny // ir.DFVal → core.DFValAny +irDFVal.asVal[MyDFType, MyModifier] // ir.DFVal → typed core.DFVal[T, M] +irDFVal.asFE[MyCoreDFType] // general ir member → core frontend type + +// Core → IR (unwrapping) +coreDFVal.asIR // core.DFVal[T,M] → ir.DFVal (throws on DFError) + +// Registering in DB +irMember.addMember // (using DFC) adds IR member to the current MutableDB +``` + +### Why the split matters for stage authors + +Stage `transform` methods work **entirely in the IR layer**: they receive `ir.DB`, pattern-match +on `ir.DFMember` subtypes, and return a patched `ir.DB`. No `DFC` is required. + +`MetaDesign` is the **only place** where you cross from IR into Core: inside a MetaDesign body you +write Core DSL code (which needs `DFC`), and `.patch` converts the result back to a +`(ir.DFMember, Patch)` pair that the stage can use. + +The conversion idiom you will see most often in stage code: +```scala +// cross from IR into core to reference an existing member inside a MetaDesign +existingIRVal.asValAny // wrap for DSL use — does NOT add to DB +plantMember(existingIRVal) // wrap AND register under the MetaDesign owner + +// cross from core back to IR to build a Patch +dsn.plantedNewVar.asIR // get the IR DFVal that was created inside the MetaDesign +``` + +--- + +## Transformation Patterns + +> **IR member hierarchy, field definitions, and pattern-match extractors → see `/ir-reference`.** + +### Pattern 1 — Simple member replacement (most common) +Collect patch entries for every matching member, then apply: + +```scala +def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = + val patchList: List[(DFMember, Patch)] = + designDB.members.view.flatMap { + case m @ SomePattern(...) => + Some(m -> Patch.Replace(m.copy(...), Patch.Replace.Config.FullReplacement)) + case _ => None + }.toList + designDB.patch(patchList) +``` + +### Pattern 2 — Move members (e.g. hoisting declarations) +```scala +designDB.members.view + .collect { case m @ DclVar() => m } + .flatMap { dcl => + dcl.getOwnerBlock match + case cb: DFConditional.Block => + Some(cb.getTopConditionalHeader -> Patch.Move(dcl, Patch.Move.Config.Before)) + case _ => None + }.toList +``` + +### Pattern 3 — Construct new members with `MetaDesign` +```scala +designDB.members.view.flatMap { + case target @ MatchPattern() => + val dsn = new MetaDesign( + target, + Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.ChangeRefAndRemove) + ): + val reg = someType.<>(VAR.REG) + reg.din := target.asValAny + Some(dsn.patch) + case _ => None +}.toList +``` + +### Pattern 4 — Type replacement (for opaque/struct type changes) +```scala +object MyDFTypeReplacement extends ComposedDFTypeReplacement( + preCheck = { case dt: DFOpaque if pred(dt) => Some(()); case _ => None }, + updateFunc = { case (dt: DFOpaque, _) => dt.actualType } +) +// Then match on values and call dfVal.updateDFType(newType) +``` + +### Pattern 5 — Backend-conditional logic +```scala +def transform(designDB: DB)(using MemberGetSet, co: CompilerOptions): DB = + val isVHDL = co.backend.isVHDL + val isVerilogOld = co.backend match + case be: dfhdl.backends.verilog => + be.dialect match + case VerilogDialect.v95 | VerilogDialect.v2001 => true + case _ => false + case _ => false + ... +``` + +### Pattern 6 — Recursive application (until stable) +Use when removing one member may expose additional members to remove: + +```scala +def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = + val patchList = designDB.members.flatMap { ... } + if (patchList.isEmpty) designDB + else + val newDB = designDB.patch(patchList) + transform(newDB)(using newDB.getSet) +``` + +### Pattern 7 — Multi-phase transformation +Apply phase 1, create a new `MemberGetSet`, then apply phase 2: + +```scala +val phase1DB = designDB.patch(phase1Patches) +locally { + given MemberGetSet = phase1DB.getSet + val phase2Patches = phase1DB.members.flatMap { ... } + phase1DB.patch(phase2Patches) +} +``` + +### Pattern 8 — `runCondition` for conditional stages +```scala +override def runCondition(using co: CompilerOptions): Boolean = + co.dropUserOpaques || co.backend match + case be: dfhdl.backends.verilog => be.dialect == VerilogDialect.v95 + case _ => false +``` + +### Pattern 9 — One-level-at-a-time repeated patching (`@tailrec`) + +Use when a transformation must be applied iteratively, processing one nesting level per pass +(e.g. flattening a hierarchy depth-by-depth). This avoids duplicate entries that would arise +if you tried to simultaneously move a parent block (with its descendant as part of the moved +list) AND separately move that same descendant. + +```scala +import scala.annotation.tailrec + +@tailrec private def flattenRepeatedly(db: DB)(using RefGen): DB = + given MemberGetSet = db.getSet + val patches = db.members.view.flatMap { + case pb: ProcessBlock if pb.isInRTDomain => collectOneLevelPatches(pb) + case _ => Nil + }.toList + if patches.isEmpty then db + else flattenRepeatedly(db.patch(patches)) + +private def collectOneLevelPatches(pb: ProcessBlock)(using MemberGetSet): List[(DFMember, Patch)] = + // Only look at direct children of pb-level steps; lift each one level up. + // After one pass, previously-nested blocks are now direct pb children, + // and their children become the candidates for the next pass. + pb.members(MemberView.Folded).flatMap { + case parent: StepBlock if parent.isRegular => + parent.members(MemberView.Folded).flatMap { + case child: StepBlock if child.isRegular => + val allMembersToMove = child :: child.members(MemberView.Flattened) + List(parent -> Patch.Move(allMembersToMove, child.getOwner, Patch.Move.Config.After)) + case _ => Nil + } + case _ => Nil + } +``` + +Key invariants: +- Include ALL descendants in `allMembersToMove` to preserve the flat-list ownership ordering. +- Process only **direct children** per pass — do not recurse into grandchildren in the same pass. +- The `@tailrec` loop terminates when `collectOneLevelPatches` returns an empty list (all blocks + are already direct children of the `ProcessBlock`). +- `given MemberGetSet = db.getSet` must be re-established at the top of each recursive call so + navigation helpers see the updated member structure. + +### Pattern 10 — Replace a DFOwner while preserving its children + +Use when you want to swap one owner block for another (e.g. `DFForBlock` → `DFWhileBlock`) but +keep all body members in place. `ReplaceWithLast(ChangeRefAndRemove)` redirects **all** refs to +the old owner — including every child member's `ownerRef` — to the new owner (the last member +synthesised in the MetaDesign). Body members naturally remain in the flat list at their correct +positions, now re-parented to the new block. No `plantMembers` is needed. + +```scala +case forBlock: DFLoop.DFForBlock if forBlock.isInRTDomain => + val m1 = new MetaDesign( + forBlock, + Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.ChangeRefAndRemove), + dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) + ): + // preamble members (e.g. iterator VAR.REG, guard expression) ... + val whileBlock = dfhdl.core.DFWhile.Block(guard)(using dfc.setMeta(forBlock.meta)) + dfc.enterOwner(whileBlock) // required so whileBlock is a proper owner in the DB + dfc.exitOwner() + // capture M1 members for use in further patches + val newIterDclIR = m1.newIterDcl.asIR + List(m1.patch, ...) +``` + +The `dfc.enterOwner` / `dfc.exitOwner` pair is required for the new owner (here `whileBlock`) to +be registered in the DB's ownership context so that child member navigation works correctly. + +### Pattern 11 — Two-MetaDesign pattern: append members at end of a re-owned body + +Use when you need to inject members **after the last body member** of a block that is being +replaced by Pattern 10. Anchor M2 at `bodyMembers.last` with `After`. M2's `injectedOwner` is +`bodyMembers.last.getOwner = oldBlock`. When M1's `ChangeRefAndRemove` redirects refs to the old +block → new block, M2's members' `ownerRef`s are also redirected, placing them correctly as the +last statements inside the new block. + +```scala +val forBodyMembers = forBlock.members(MemberView.Folded) +// M1: replace forBlock with whileBlock (Pattern 10) ... +if forBodyMembers.nonEmpty then + val m2 = new MetaDesign( + forBodyMembers.last, + Patch.Add.Config.After, + dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) + ): + // members here are owned by forBodyMembers.last.getOwner = forBlock + // after M1's ChangeRefAndRemove, their ownerRefs are redirected to whileBlock + m1.newIterDcl.din := m1.newIterDcl + stepConst // cross-MetaDesign ref is fine + List(m1.patch, iterDclPatch, m2.patch) +else List(m1.patch, iterDclPatch) +``` + +**Cross-MetaDesign references**: Members declared with `val` in M1 body are accessible as +`m1.memberName` (MetaDesign extends `reflect.Selectable`). They can be used inside M2's body +via closure; the symbolic refs resolve correctly in the final DB because M1's members are present +after all patches are applied. + +**Returning multiple patches per case**: use `.flatMap { ... }` (not `.collect { ... }.flatten`) +and return `List(...)` from the case; return `None` from the catch-all `case _ =>`. Both `Option` +and `List` are `IterableOnce`, so `flatMap` handles them uniformly. + +--- + +## Boilerplate Template + +### Stage file: `compiler/stages/src/main/scala/dfhdl/compiler/stages/MyStage.scala` + +### Stage ScalaDoc guidelines + +- Wrap the ScalaDoc comment with `//format: off` / `//format: on` (scalafmt reformats `{{{ }}}` blocks and `==Headings==` if left unguarded). +- Use `==Rule N: Title==` ScalaDoc sections for each distinct transformation rule. +- Use `{{{ }}}` code blocks (not ` ```scala ` fences) for before/after examples. +- Each code block should start with `// Before` and `// After` comment lines. +- Before/after examples should show the construct in context (e.g. with a preceding statement), not as the first and only statement in a process — this makes the placement of generated members unambiguous. +- Do NOT include Idempotency or Determinism sections — those are implementation invariants, not user-facing documentation. + +```scala +package dfhdl.compiler.stages + +import dfhdl.compiler.analysis.* +import dfhdl.compiler.ir.* +import dfhdl.compiler.patching.* +import dfhdl.options.CompilerOptions + +//format: off +/** Brief description of what this stage does and which IR shapes it transforms. + * + * ==Rule 1: Title of first transformation== + * + * Prose explanation of the rule. + * {{{ + * // Before + * + * + * // After + * + * }}} + * + * ==Rule 2: Title of second transformation== + * ... + */ +//format: on +case object MyStage extends Stage: + def dependencies: List[Stage] = List(SomePriorStage) // run these first + def nullifies: Set[Stage] = Set(SomeInvalidatedStage) + + def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = + val patchList: List[(DFMember, Patch)] = + designDB.members.view.flatMap { + case m @ TargetPattern(...) => + Some(m -> Patch.Replace(m.copy(/* changes */), Patch.Replace.Config.FullReplacement)) + case _ => None + }.toList + designDB.patch(patchList) +end MyStage + +// Convenience extension — follows the naming convention of all other stages +extension [T: HasDB](t: T) + def myStage(using CompilerOptions): DB = + StageRunner.run(MyStage)(t.db) +``` + +--- + +## Test Authoring Rules + +**Tests must be self-contained.** Each test should only exercise the stage under test. Do not write input designs that rely on a prior stage to produce the IR shape that the current stage expects — write that IR shape directly using the DFHDL DSL. + +For example, a stage that operates on `StepBlock`s should use explicit `def MyStep: Step = …` syntax in the test design rather than `1.cy.wait` or `while (…)` constructs, because those are transformed into `StepBlock`s by `DropRTWaits`, not by the stage under test. Letting `DropRTWaits` silently run as a dependency makes a failing test ambiguous: it is unclear whether the bug is in the stage under test or in the dependency. + +The same principle applies to any other prior-stage construct: if `DropFoo` normally feeds into `AddBar`, the `AddBarSpec` tests should express the output of `DropFoo` directly, without triggering `DropFoo`. + +## Test File Template + +### Test file: `compiler/stages/src/test/scala/StagesSpec/MyStageSpec.scala` + +```scala +package StagesSpec + +import dfhdl.* +import dfhdl.compiler.stages.myStage +// scalafmt: { align.tokens = [{code = "<>"}, {code = "="}, {code = "=>"}, {code = ":="}]} + +class MyStageSpec extends StageSpec: + + test("basic transformation") { + class Top extends EDDesign: // or RTDesign / DFDesign + val x = UInt(8) <> IN + val y = UInt(8) <> OUT + process(all): + y :== x + val result = (new Top).myStage + assertCodeString( + result, + """|class Top extends EDDesign: + | val x = UInt(8) <> IN + | val y = UInt(8) <> OUT + | process(all): + | y :== x + |end Top + |""".stripMargin + ) + } + + test("backend-specific behaviour under VHDL") { + given options.CompilerOptions.Backend = backends.vhdl.v93 + class Top extends EDDesign: + ... + val result = (new Top).myStage + assertCodeString(result, "...") + } + +end MyStageSpec +``` + +### `StageSpec` reference +```scala +// compiler/stages/src/test/scala/StageSpec.scala +abstract class StageSpec(stageCreatesUnrefAnons: Boolean = false) + extends FunSuite, NoTopAnnotIsRequired: + inline def assertCodeString(db: DB, cs: String): Unit = + import db.getSet + if (stageCreatesUnrefAnons) db.dropUnreferencedAnons.sanityCheck + else db.sanityCheck + assertNoDiff(DefaultPrinter.csDB, cs) +``` + +- Pass `stageCreatesUnrefAnons = true` when your stage intentionally leaves unreferenced anonymous members (e.g. it extends `NoCheckStage`). `assertCodeString` will then call `dropUnreferencedAnons` automatically before checking. +- `assertNoDiff` from munit gives a clear character-level diff on mismatch. +- The expected code string must reproduce DFHDL source syntax exactly (including `end DesignName` terminators, blank lines between designs, and pipe-aligned fields when using the scalafmt alignment hint at the top of the file). + +--- + +## Common Mistakes to Avoid + +1. **Writing `end process` in code examples** — Scala does not support `end` markers on method + calls, so `process` blocks have no closing `end process`. Only named definitions (`def`, `class`, + `object`, `val`, etc.) accept end markers. StepBlock prints as `def Name: Step = … end Name`; + ProcessBlock prints as `process(sensitivity):\n body` with no end marker. +2. **Iterating over `Set` or `Map` to build the patch list** — order is undefined, producing non-deterministic output. Always iterate `designDB.members` (a `List`) to drive patch collection. +3. **Overly broad pattern matches** — if a match can fire on already-transformed IR, the stage is not idempotent (`f(f(x)) ≠ f(x)`). Match on the exact source shape so that the output IR no longer satisfies the predicate. +4. **Forgetting `end transform` / `end MyStage`** — scalafmt's `insertEndMarkerMinLines = 15` rule requires end markers on blocks ≥ 15 lines. +5. **Missing `given RefGen = RefGen.fromGetSet`** inside `transform` when using `MetaDesign` with fresh reference generation. +6. **Mutating `getSet`** — always create a new `MemberGetSet` via `newDB.getSet` after patching before iterating again. +7. **Patch order matters** — patches are applied in list order; a `Move` before a `Replace` on the same member may conflict. +8. **Not extending `NoCheckStage`** when your stage produces unreferenced anonymous members as intermediate output — the automatic sanity check will fail. +9. **Nullifying too aggressively** — only nullify a stage if your transformation invalidates its invariant. Unnecessary nullification forces redundant re-runs. +10. **Confusing `ChangeRefOnly` vs `FullReplacement`** — `ChangeRefOnly` keeps the old member in the member list (useful when another stage still expects it); `FullReplacement` swaps it in place. +11. **Moving a `DFOwner` without its descendants** — `Patch.Move` only moves the listed members. + Moving a block but leaving its children at their original positions violates the ownership + invariant (pre-order DFS ordering). Always include `block :: block.members(MemberView.Flattened)` + in the moved-members list. If you need to move multiple nesting levels, use the one-level-at-a-time + `@tailrec` pattern (Pattern 9) to avoid duplicating descendants across multiple Move entries. +12. **Conflicting patches on the same member** — `Patch.Move` internally generates `Patch.Remove` + for each moved member. If that same member also appears in another patch in the same list, you + get `IllegalArgumentException: Received two different patches for the same member`. The most + common cause is using a `Goto` as a `Move.Before` anchor while that same `Goto` also appears + in another Move's members list. Fix by splitting into sequential `db.patch()` calls (multi-phase). +13. **Using `dfc.owner.ref` inside MetaDesign** — `import dfhdl.core.*` introduces extension + methods that conflict with `.ref`. Use `dfc.ownerOrEmptyRef` instead to obtain the owner's + `ir.DFOwner.Ref` when constructing raw IR members. +14. **`DFBlock` subtypes lack `isAnonymous` / `getName`** — these helpers are extension methods on + `DFMember.Named` (value types), not on blocks. For `DFForBlock`, `DFWhileBlock`, and similar, + access the name via `block.meta.nameOpt` directly: + ```scala + val iterName = forBlock.meta.nameOpt match + case None => iteratorDcl.getName + case Some(name) => s"${name}_${iteratorDcl.getName}" + ``` +15. **Case-class extractor `DFForBlock()` requires all fields** — writing `case x @ DFLoop.DFForBlock()` + fails with "wrong number of argument patterns" because `DFForBlock` has multiple fields. Use a + type pattern instead: `case x: DFLoop.DFForBlock`. The same applies to other multi-field IR + case classes that have no dedicated single-argument unapply. +16. **Assuming duplicate designs have members** — designs tagged `DuplicateTag` have **no members** + in the DB (ports, domain blocks, and values are removed during immutable DB creation). Use + `db.dupPortsByName` for port lookups (works for both origin and duplicate designs) and + `db.dupDomainOwnerPublicMemberList` for domain owner analysis. If your stage needs the full + member hierarchy of duplicates (e.g. for parameter analysis), you must reconstruct it by + duplicating the origin design's members with `copyWithNewRefs` — see `GlobalizePortVectorParams` + for the pattern. Never iterate `designMemberTable` or `domainOwnerMemberTable` expecting to + find members for duplicate designs. + +--- + +## API Notes + +### `dfhdl.core.DFInt32` + +Available as both a type alias and a value (`ir.DFInt32.asFE[DFInt32]`). Use it inside MetaDesign +bodies to create 32-bit signed integer variables: + +```scala +dfhdl.core.DFInt32.<>(VAR.REG).initForced(List(initConst))(using dfc.setName("i")) +``` + +This mirrors the `iterType.<>(VAR.REG)` pattern used for UInt variables. + +--- + +## Checklist for a New Stage + +- [ ] Stage `object`/`class` extends `Stage` (or `NoCheckStage` / `BundleStage` if appropriate) +- [ ] `dependencies` lists every stage that must run first +- [ ] `nullifies` lists every stage whose invariant this transformation breaks +- [ ] `runCondition` added if the stage should be skipped for some backends/options +- [ ] `transform` builds `patchList` and calls `designDB.patch(patchList)` +- [ ] Patch list is derived from `designDB.members` (ordered `List`) — never from an unordered `Set` or `Map` **[determinism]** +- [ ] Pattern matches target the *source* form only; the transformed IR should not re-match the same predicate, so `f(f(x)) == f(x)` **[idempotency]** +- [ ] Convenience `extension` method added at the bottom of the file +- [ ] Test file in `StagesSpec/` extends `StageSpec` +- [ ] At least one "basic" test and one "edge case" or "backend-specific" test +- [ ] `assertCodeString` expected strings verified manually or via a first-run snapshot +- [ ] `sbt test` passes (or `sbt quickTestSetup; test` for faster iteration via `lib/Playground.scala`) +- [ ] **Update this skill** with any general lessons learned (see below) + +--- + +## Keeping This Skill Up to Date + +While creating a new stage, you will often discover things that are not yet documented here: +new IR API behaviours, patch interaction subtleties, MetaDesign patterns, common pitfalls, etc. + +**After completing a stage, review what you learned and update this file** when the lesson is +general enough to help with future stage creation. Specifically: + +- **New IR / API behaviour** → add to the relevant section (IR Data Model, Patch System, MetaDesign). +- **New pitfall or gotcha** → add a numbered entry to "Common Mistakes to Avoid". +- **New reusable transformation pattern** → add a numbered "Pattern N" entry under "Transformation Patterns". +- **Corrected or outdated information** → update the existing section in place. + +Do **not** add stage-specific implementation details here. Only document things that are likely +to recur in other stages. The rule of thumb: *would a future contributor hit this same issue if +they didn't already know about it?* If yes, document it. + +Also update **stage-adjacent source files** when you discover missing or incorrect API documentation: +- `Patch.scala` — document `Patch.Move` / `Patch.Replace` / `Patch.Add` semantics +- `DB.scala` — document `MemberView.Folded` / `Flattened` +- `MetaDesign.scala` — document ownership and `plantMember` / `plantClonedMembers` +- `DFC.scala` — document DFC helpers used in MetaDesign + +This skill and the source files it references are the authoritative guide for future stage authors. +Keep them accurate. diff --git a/.scalafmt.conf b/.scalafmt.conf index 1db933e40..88fe92390 100755 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = 3.10.5 +version = 3.10.7 runner.dialect = scala3 maxColumn = 100 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..0eabac6c4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,116 @@ +# DFHDL — Claude Code Guide + +> **For contributors and Claude Code users working on the DFHDL project.** +> This file is version-controlled — keep it updated as the project structure evolves. +> Skills for deeper topics live in [.claude/commands/](.claude/commands/). + +## Project Overview + +**DFHDL (DFiant HDL)** is a dataflow hardware description language embedded as a Scala 3 library. It provides timing-agnostic and device-agnostic hardware design with three levels of abstraction: + +- **Dataflow (DF)**: Timing-agnostic, uses dataflow firing rules +- **Register-Transfer (RT)**: Equivalent to Chisel/Amaranth +- **Event-Driven (ED)**: Equivalent to Verilog/VHDL + +Outputs: Verilog, SystemVerilog, VHDL. + +## Build System + +**Tool**: SBT 1.12.2 — **Scala**: 3.8.1 (nightly resolver enabled) + +```bash +sbt compile # compile all subprojects +sbt test # run all unit tests +sbt testApps # run simulation/app tests (requires OSS CAD tools) +sbt quickTestSetup # limit test scope to lib/Playground.scala only (fast iteration) +sbt clearSandbox # delete sandbox/ directory +sbt docExamplesRefUpdate # copy generated HDL from sandbox/ to lib/src/test/resources/ref/ +``` + +## Subproject Structure + +Dependencies flow left to right: + +``` +internals → plugin → compiler_ir → core → compiler_stages → lib → platforms + → ips +``` + +| Subproject | SBT name | Directory | Purpose | +|---|---|---|---| +| internals | `internals` | `internals/` | Core utilities: BitVector, MetaContext, DiskCache, etc. | +| plugin | `plugin` | `plugin/` | Scala 3 compiler plugin (9 phases) | +| compiler_ir | `compiler_ir` | `compiler/ir/` | IR/AST data structures, type system | +| core | `core` | `core/` | HDL language abstractions (DFVal, DFType, Design) | +| compiler_stages | `compiler_stages` | `compiler/stages/` | 50+ transformation stages for code generation | +| lib | `lib` | `lib/` | Standard library: arithmetic, memory, ALU, crypto | +| platforms | `platforms` | `platforms/` | FPGA board wrappers (Apache 2.0 licensed) | +| ips | `ips` | `ips/` | IP cores library | + +## Compiler Plugin Phases + +Located in `plugin/src/main/scala/plugin/`: + +1. `PreTyperPhase` — pre-typing transformations +2. `TopAnnotPhase` — top-level annotation processing +3. `MetaContextPlacerPhase` — places meta-context markers +4. `LoopFSMPhase` — loop-to-FSM transformations +5. `CustomControlPhase` — custom control flow +6. `DesignDefsPhase` — design definition processing +7. `MetaContextDelegatePhase` — meta-context delegation +8. `MetaContextGenPhase` — meta-context code generation +9. `OnCreateEventsPhase` — on-create event handling + +The plugin is applied to `core`, `compiler_stages`, `lib`, `platforms`, and `ips` via `pluginUseSettings` / `pluginTestUseSettings`. + +## Testing + +**Framework**: munit 1.2.2 + +- **Stage tests**: `compiler/stages/src/test/scala/StagesSpec/` — tests each compiler stage +- **Doc example tests**: `lib/src/test/scala/docExamples/` — validates documentation examples +- **Arithmetic tests**: `lib/src/test/scala/ArithSpec/` +- **AES tests**: `lib/src/test/scala/AES/` +- **Base class**: `DesignSpec` — provides `assertCodeString()` and `assertElaborationErrors()` +- **Playground**: `lib/src/test/scala/Playground.scala` — used for quick local iteration via `quickTestSetup` + +Generated HDL reference files live in `lib/src/test/resources/ref/`. Update them with `sbt docExamplesRefUpdate` after intentional output changes. + +`testApps` auto-detects installed simulation tools (ghdl, nvc, verilator, iverilog, questa, vivado) and runs the AES cipher simulation against all available tool/dialect combinations. + +## Code Conventions + +- **Formatting**: scalafmt 3.10.6, max 100 columns, Scala 3 dialect + - Optional braces removed (`removeOptionalBraces = oldSyntaxToo`) + - End markers inserted for blocks ≥ 15 lines + - Run `scalafmt` before committing +- **Compiler flags**: `-language:strictEquality`, `-unchecked`, `-feature`, `-preview`, `-deprecation` +- **Implicit conversions**: only enabled in `internals` and `compiler_ir` via `implicitConversionSettings` +- **Naming**: `DF`-prefixed types (e.g., `DFVal`, `DFType`), `DFC` for context; stage names follow `Drop*`, `Add*`, `Connect*`, `Break*` patterns +- **Package root**: `dfhdl.*` + +## Key Files + +| File | Purpose | +|---|---| +| `build.sbt` | Multi-project build definition | +| `project/DFHDLCommands.scala` | Custom SBT commands | +| `.scalafmt.conf` | Code formatting rules | +| `mkdocs.yml` | Documentation site config | +| `sandbox/` | Generated output during tests/apps (gitignored, cleared by `clearSandbox`) | +| `lib/src/test/resources/ref/` | Reference HDL output snapshots for regression tests | + +## External Simulation Tools (for `testApps`) + +CI installs these via OSS CAD Suite: +- **Verilog**: verilator, iverilog (sv2005 skipped for iverilog), questa, vivado +- **VHDL**: ghdl, nvc, questa, vivado (v2008 skipped for vivado) + +## Claude Instructions + +- When asked to **create a new compiler stage** or **modify an existing compiler stage**, always invoke the `/new-stage` skill before doing any work. + +## Licenses + +- Main library (`internals`, `plugin`, `compiler_ir`, `core`, `compiler_stages`, `lib`, `ips`): **LGPL v3.0** +- `platforms/`: **Apache 2.0** diff --git a/build.sbt b/build.sbt index fc5d2ccf1..64e17d8a4 100755 --- a/build.sbt +++ b/build.sbt @@ -144,11 +144,11 @@ lazy val platforms = project lazy val dependencies = new { private val scodecV = "1.2.4" - private val munitV = "1.2.2" - private val airframelogV = "2025.1.27" - private val oslibV = "0.11.7" - private val scallopV = "5.3.0" - private val upickleV = "4.4.2" + private val munitV = "1.2.4" + private val airframelogV = "2026.1.4" + private val oslibV = "0.11.8" + private val scallopV = "6.0.0" + private val upickleV = "4.4.3" val scodec = "org.scodec" %% "scodec-bits" % scodecV val munit = "org.scalameta" %% "munit" % munitV % Test diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala index 110b92bb8..6a0a50d5c 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/analysis/DFValAnalysis.scala @@ -65,7 +65,7 @@ object StrippedPortByNameSelect: object DefaultOfDesignParam: def unapply(dfVal: DFVal)(using MemberGetSet): Option[DFVal.DesignParam] = dfVal.originMembers.collectFirst { - case dp: DFVal.DesignParam if dp.defaultRef.get == dfVal => dp + case dp: DFVal.DesignParam if dp.defaultValRef.get == dfVal => dp } object OpaqueActual: @@ -84,8 +84,7 @@ object AsOpaque: object Bind: def unapply(alias: DFVal.Alias)(using MemberGetSet): Option[DFVal] = - if (alias.getTagOf[BindTag].isDefined) - Some(alias.relValRef.get) + if (alias.hasTagOf[BindTag]) Some(alias.relValRef.get) else None object ClkEdge: @@ -200,6 +199,8 @@ extension (member: DFMember) def originMembersNoTypeRef(using MemberGetSet): Set[DFMember] = getSet.designDB.originMemberTableNoTypeRef.getOrElse(member, Set()) +type DFValReadDep = TextOut | DFNet | DFVal | DFConditional.Block + extension (dfVal: DFVal) def getPartialAliases(using MemberGetSet): Set[DFVal.Alias.Partial] = dfVal.originMembers.flatMap { @@ -234,8 +235,8 @@ extension (dfVal: DFVal) dfVal.originMembers.view .collect { case dfVal: DFVal => dfVal } .exists(dfVal => cond(dfVal) || dfVal.existsInComposedReadDeps(cond)) - def getReadDeps(using MemberGetSet): Set[TextOut | DFNet | DFVal | DFConditional.Block] = - val fromRefs: Set[TextOut | DFNet | DFVal | DFConditional.Block] = + def getReadDeps(using MemberGetSet): Set[DFValReadDep] = + val fromRefs: Set[DFValReadDep] = dfVal.originMembersNoTypeRef.flatMap { case net: DFNet => net match @@ -260,12 +261,13 @@ extension (dfVal: DFVal) .toSet ++ fromRefs case _ => fromRefs end getReadDeps - def isReferencedByAnyDcl(using MemberGetSet): Boolean = + def isReferencedByAnyDclOrDesign(using MemberGetSet): Boolean = dfVal.originMembers.view.exists { - case _: DFVal.Dcl => true - case DclConst() => true - case dfVal: DFVal => dfVal.isReferencedByAnyDcl - case _ => false + case _: DFVal.Dcl => true + case DclConst() => true + case _: DFDesignBlock => true + case dfVal: DFVal => dfVal.isReferencedByAnyDclOrDesign + case _ => false } @tailrec private def flatName(member: DFVal, suffix: String)(using MemberGetSet): String = @@ -415,11 +417,12 @@ extension (origVal: DFVal) forceIncludeOrigVal: Boolean )(using MemberGetSet): List[DFVal] = if (origVal.isAnonymous && !origVal.isGlobal || forceIncludeOrigVal) - origVal :: origVal.getRefs.map(_.get).view - .flatMap { - case dfVal: DFVal => dfVal.collectRelMembersRecur(false) - case _ => Nil - }.toList + origVal :: + origVal.getRefs.map(_.get).view + .flatMap { + case dfVal: DFVal => dfVal.collectRelMembersRecur(false) + case _ => Nil + }.toList else Nil @targetName("collectRelMembersDFVal") def collectRelMembers(includeOrigVal: Boolean)(using MemberGetSet): List[DFVal] = diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala index 0adc121ad..f17073d71 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DB.scala @@ -43,14 +43,6 @@ final case class DB( // considered to be in build if not in simulation and has a device constraint lazy val inBuild: Boolean = !inSimulation && top.isDeviceTop - lazy val portsByName: Map[DFDesignInst, Map[String, DFVal.Dcl]] = - members.view - .collect { case m: DFVal.Dcl if m.isPort => m } - .groupBy(_.getOwnerDesign) - .map { case (design, dcls) => - design -> dcls.map(m => m.getRelativeName(design) -> m).toMap - }.toMap - lazy val topIOs: List[DFVal.Dcl] = designMemberTable(top).collect { case dcl: DFVal.Dcl if dcl.isPort => dcl } @@ -124,7 +116,8 @@ final case class DB( if (owner == namedDFTypeMember.getOwnerDesign) namedDFTypeMap // same design block -> nothing to do else - namedDFTypeMap + (dfType -> None) // used in more than one block -> global named type + namedDFTypeMap + + (dfType -> None) // used in more than one block -> global named type case Some(None) => namedDFTypeMap // known to be a global type // found new named type case None => @@ -263,13 +256,114 @@ final case class DB( List(top -> List()) ).reverse + // holds a hash table that lists members of each owner block. The member list order is maintained. + lazy val designMemberTable: Map[DFDesignBlock, List[DFMember]] = + Map(designMemberList*) + // holds the topological order of unique design block dependency lazy val uniqueDesignMemberList: List[(DFDesignBlock, List[DFMember])] = designMemberList.filterNot(_._1.isDuplicate) - // holds a hash table that lists members of each owner block. The member list order is maintained. - lazy val designMemberTable: Map[DFDesignBlock, List[DFMember]] = - Map(designMemberList*) + // maps each duplicated design to its origin (first non-duplicate design with the same dclName) + lazy val dupDesignToOrigMap: Map[DFDesignBlock, DFDesignBlock] = + val origByName = + uniqueDesignMemberList.map(_._1).groupBy(_.dclName).view.mapValues(_.head).toMap + designMemberList.collect { + case (design, _) if design.isDuplicate => design -> origByName(design.dclName) + }.toMap + + // Single pass: for each duplicate design, compute dup domain blocks AND dup ports together. + // dupDesignDomainBlockMap: maps (dupDesign, origDomainBlock) -> dupDomainBlock + // dupPortsByName: maps design -> port name -> Dcl (including dup entries with DuplicationRef) + lazy val (dupDesignDomainBlockMap, dupPortsByName) = + val origDomainBlocks: Map[DFDesignBlock, List[DomainBlock]] = + members.view + .collect { case db: DomainBlock => db } + .groupBy(_.getOwnerDesign) + .map { (design, blocks) => design -> blocks.toList } + .toMap + val origPortMap = members.view + .collect { case m: DFVal.Dcl if m.isPort => m } + .groupBy(_.getOwnerDesign) + .map { case (design, dcls) => + design -> ListMap.from(dcls.view.map(m => m.getRelativeName(design) -> m)) + }.toMap + // PBNS members carry the correct dfType for each duplicate's port (reflecting the + // actual instantiation parameters), so we use it to override the origin Dcl's dfType. + val pbnsByDesign = members.view + .collect { case m: DFVal.PortByNameSelect => m } + .groupBy(m => m.designInstRef.get) + .view.mapValues(_.map(m => m.portNamePath -> m.dfType).toMap).toMap + val domainBlockMap = mutable.Map.empty[(DFDesignBlock, DomainBlock), DomainBlock] + val portEntries = mutable.Map.empty[DFDesignInst, ListMap[String, DFVal.Dcl]] + dupDesignToOrigMap.foreach { (dupDesign, origDesign) => + // 1. Build domain block copies + val origToDupMap = mutable.Map.empty[DFDomainOwner, DFDomainOwner] + origToDupMap += origDesign -> dupDesign + origDomainBlocks.getOrElse(origDesign, Nil).foreach { origBlock => + val dupOwner = origToDupMap(origBlock.getOwnerDomain) + val dupBlock = origBlock.copy(ownerRef = DFRef.DuplicationRef(dupOwner)) + origToDupMap += origBlock -> dupBlock + domainBlockMap += (dupDesign, origBlock) -> dupBlock + } + // 2. Build port copies (reusing origToDupMap from step 1) + val pbnsTypes = pbnsByDesign.getOrElse(dupDesign, Map.empty) + portEntries += dupDesign -> ListMap.from(origPortMap(origDesign).view.map { (name, dcl) => + val dfType = pbnsTypes.getOrElse(name, dcl.dfType) + val dupOwnerDomain = origToDupMap(dcl.getOwnerDomain) + name -> dcl.copy(ownerRef = DFRef.DuplicationRef(dupOwnerDomain), dfType = dfType) + }) + } + // dupEntries only fills in missing entries (designs without real port members) + (domainBlockMap.toMap, portEntries.toMap ++ origPortMap) + + lazy val dupDomainOwnerPublicMemberList: List[(DFDomainOwner, List[DFMember])] = + def publicMemberFilter(member: DFMember): Boolean = + member match + case dcl: DFVal.Dcl if dcl.isPort => true + case design: DFDesignBlock => true + case domainBlock: DomainBlock => true + case _ => false + // For duplicate designs, generate entries for their dup-copy domain blocks + // by mirroring the origin's domain owner structure. + def dupEntriesFor( + origOwner: DFDomainOwner, + dupDesign: DFDesignBlock + ): List[(DFDomainOwner, List[DFMember])] = + val dupOwner: DFDomainOwner = origOwner match + case _: DFDesignBlock => dupDesign + case db: DomainBlock => dupDesignDomainBlockMap((dupDesign, db)) + case _: DFInterfaceOwner => ??? // TODO + val origMembers = domainOwnerMemberTable(origOwner) + .view.filter(publicMemberFilter).toList + val dupDesignPorts = dupPortsByName.getOrElse(dupDesign, ListMap.empty) + val dupMembers = origMembers.map { + case dcl: DFVal.Dcl => + val relName = dcl.getRelativeName(origOwner.getThisOrOwnerDesign) + dupDesignPorts.getOrElse(relName, dcl) + case design: DFDesignBlock => design // nested designs stay as-is + case db: DomainBlock => dupDesignDomainBlockMap((dupDesign, db)) + case m => m + } + (dupOwner -> dupMembers) :: origMembers.collect { case db: DomainBlock => + dupEntriesFor(db, dupDesign) + }.flatten + domainOwnerMemberList.flatMap { case (owner, members) => + owner match + case dupDesign: DFDesignBlock if dupDesign.isDuplicate => + val origDesign = dupDesignToOrigMap(dupDesign) + dupEntriesFor(origDesign, dupDesign) + case origDesign: DFDesignBlock => + List(origDesign -> members.filter(publicMemberFilter)) + case origDomainBlock: DomainBlock => + List(origDomainBlock -> members.filter(publicMemberFilter)) + // TODO: missing interface handling + case interface: DFInterfaceOwner => ??? + } + end dupDomainOwnerPublicMemberList + + lazy val dupDomainOwnerPublicMemberTable: Map[DFDomainOwner, List[DFMember]] = + Map(dupDomainOwnerPublicMemberList*) private def conditionalChainGen: Map[DFConditional.Header, List[DFConditional.Block]] = val handled = mutable.Set.empty[DFConditional.Block] @@ -637,24 +731,25 @@ final case class DB( ) } // collect all ports that are not connected directly or implicitly as magnets - val danglingPorts = members.collect { - case p: DFVal.Dcl - if p.isPortIn && !p.isClkDcl && !p.isRstDcl && !connectionTable.contains(p) && - !p.getOwnerDesign.isTop && !magnetConnectionTable.contains(p) => - val ownerDesign = p.getOwnerDesign - s"""|DFiant HDL connectivity error! - |Position: ${ownerDesign.meta.position} - |Hierarchy: ${ownerDesign.getFullName} - |Message: Found a dangling (unconnected) input port `${p.getName}`.""".stripMargin - case p: DFVal.Dcl - if p.isPortOut && !p.getOwnerDesign.isDuplicate && !p.getOwnerDesign.isBlackBox && - !connectionTable.contains(p) && !assignmentsDclTable.contains(p) && - !magnetConnectionTable.contains(p) && !p.hasNonBubbleInit => - val ownerDesign = p.getOwnerDesign - s"""|DFiant HDL connectivity error! - |Position: ${p.meta.position} - |Hierarchy: ${ownerDesign.getFullName} - |Message: Found a dangling (unconnected/unassigned and uninitialized) output port `${p.getName}`.""".stripMargin + val danglingPorts = dupPortsByName.view.flatMap { (ownerDesign, ports) => + ports.collect { + case (_, p: DFVal.Dcl) + if p.isPortIn && !p.isClkDcl && !p.isRstDcl && !connectionTable.contains(p) && + !ownerDesign.isTop && !magnetConnectionTable.contains(p) => + s"""|DFiant HDL connectivity error! + |Position: ${ownerDesign.meta.position} + |Hierarchy: ${ownerDesign.getFullName} + |Message: Found a dangling (unconnected) input port `${p.getName}`.""".stripMargin + case (_, p: DFVal.Dcl) + if p.isPortOut && !ownerDesign.isDuplicate && !ownerDesign.isBlackBox && + !connectionTable.contains(p) && !assignmentsDclTable.contains(p) && + !magnetConnectionTable.contains(p) && !p.hasNonBubbleInit => + val ownerDesign = p.getOwnerDesign + s"""|DFiant HDL connectivity error! + |Position: ${p.meta.position} + |Hierarchy: ${ownerDesign.getFullName} + |Message: Found a dangling (unconnected/unassigned and uninitialized) output port `${p.getName}`.""".stripMargin + } } if (danglingPorts.nonEmpty) throw new IllegalArgumentException( @@ -671,53 +766,51 @@ final case class DB( owner.domainType match case _: DomainType.RT => Some(owner) case _ => None - members.view.flatMap { - case domainOwner: DFDomainOwner => - domainOwner.domainType match - // only RT domain owners are saved - case DomainType.RT(cfg) => - cfg match - // derived configuration dependency is set according to various factors: - case RTDomainCfg.Derived => - domainOwner match - // for designs, the derived configuration is defined by the owner RT design, if such exists. - // if not, then there is no domain configuration dependency - case design: DFDesignBlock => - if (design.isTop) None - else design.getRTOwnerOption.map(design -> _) - // for domains, the derived configuration is defined according to the input ports source, - // if such ports exist (ignoring Clk/Rst ports). - // otherwise, the derived configuration is defined by the domain's owner. - case domain: DomainBlock => - val domainMembers = domainOwnerMemberTable(domain) - val inPorts = domainMembers.collect { - case dcl: DFVal.Dcl if dcl.isPortIn && !dcl.isClkDcl && !dcl.isRstDcl => dcl - } - val inSourceDomains = inPorts.view.flatMap { port => - connectionTable.getNets(port).headOption match - case Some(DFNet.Connection(_, from, _)) => from.getRTOwnerOption - case _ => None - }.toSet - if (inSourceDomains.isEmpty) domain.getRTOwnerOption.map(domain -> _) - else if (inSourceDomains.size > 1) - throw new IllegalArgumentException( - s"""|Found ambiguous source RT configurations for the domain: - |${domain.getFullName} - |Sources: - |${inSourceDomains.map(_.getFullName).mkString("\n")} - |Possible solution: - |Either explicitly define a configuration for the domain or drive it from a single source domain. - |""".stripMargin - ) - else Some(domain -> inSourceDomains.head) - case ifc: DFInterfaceOwner => - ??? // TODO: decide what are the rules are for interfaces - // related configuration is just dependent on the its related domain - case RTDomainCfg.Related(relatedDomainRef) => - Some(domainOwner -> relatedDomainRef.get) - case _ => None - case _ => None - case _ => None + // Use dupDomainOwnerPublicMemberList to include domain owners from duplicate designs + dupDomainOwnerPublicMemberList.view.flatMap { (domainOwner, domainMembers) => + domainOwner.domainType match + // only RT domain owners are saved + case DomainType.RT(cfg) => + cfg match + // derived configuration dependency is set according to various factors: + case RTDomainCfg.Derived => + domainOwner match + // for designs, the derived configuration is defined by the owner RT design, if such exists. + // if not, then there is no domain configuration dependency + case design: DFDesignBlock => + if (design.isTop) None + else design.getRTOwnerOption.map(design -> _) + // for domains, the derived configuration is defined according to the input ports source, + // if such ports exist (ignoring Clk/Rst ports). + // otherwise, the derived configuration is defined by the domain's owner. + case domain: DomainBlock => + val inPorts = domainMembers.collect { + case dcl: DFVal.Dcl if dcl.isPortIn && !dcl.isClkDcl && !dcl.isRstDcl => dcl + } + val inSourceDomains = inPorts.view.flatMap { port => + connectionTable.getNets(port).headOption match + case Some(DFNet.Connection(_, from, _)) => from.getRTOwnerOption + case _ => None + }.toSet + if (inSourceDomains.isEmpty) domain.getRTOwnerOption.map(domain -> _) + else if (inSourceDomains.size > 1) + throw new IllegalArgumentException( + s"""|Found ambiguous source RT configurations for the domain: + |${domain.getFullName} + |Sources: + |${inSourceDomains.map(_.getFullName).mkString("\n")} + |Possible solution: + |Either explicitly define a configuration for the domain or drive it from a single source domain. + |""".stripMargin + ) + else Some(domain -> inSourceDomains.head) + case ifc: DFInterfaceOwner => + ??? // TODO: decide what are the rules are for interfaces + // related configuration is just dependent on the its related domain + case RTDomainCfg.Related(relatedDomainRef) => + Some(domainOwner -> relatedDomainRef.get) + case _ => None + case _ => None } .toMap end dependentRTDomainOwners @@ -782,7 +875,8 @@ final case class DB( case internal: DFDesignBlock => internal.usesClkRst.usesClk case _ => false } || reversedDependents.getOrElse(domainOwner, Set()).exists(_.usesClkRst.usesClk) || - domainOwner.isTop && (domainOwner.getExplicitCfg.clkCfg match + domainOwner.isTop && + (domainOwner.getExplicitCfg.clkCfg match case ClkCfg.Explicit(inclusionPolicy = ClkRstInclusionPolicy.AlwaysAtTop) => true case _ => false) @@ -794,7 +888,8 @@ final case class DB( case internal: DFDesignBlock => internal.usesClkRst.usesRst case _ => false } || reversedDependents.getOrElse(domainOwner, Set()).exists(_.usesClkRst.usesRst) || - domainOwner.isTop && (domainOwner.getExplicitCfg.rstCfg match + domainOwner.isTop && + (domainOwner.getExplicitCfg.rstCfg match case RstCfg.Explicit(inclusionPolicy = ClkRstInclusionPolicy.AlwaysAtTop) => true case _ => false) end extension @@ -1029,9 +1124,6 @@ final case class DB( membersNoGlobals.view.drop(1).flatMap { case _: PortByNameSelect => None case m => - val isDesignParam = m match - case _: DFVal.DesignParam => true - case _ => false m.getRefs.view.map(_.get).flatMap { // global values are ok to be referenced case dfVal: DFVal.CanBeGlobal if dfVal.isGlobal => None @@ -1047,17 +1139,9 @@ final case class DB( case refMember: DFDesignBlock => if (m.isMemberOf(refMember)) None else Some(refMember) - case refMember => - m match - // design parameters are expected to reference values from their parent design - // or from the same design for default parameter values - case dp: DFVal.DesignParam => - if (refMember.isSameOwnerDesignAs(dp) && dp.defaultRef.get == refMember) None - else if (m.isOneLevelBelow(refMember)) None - else Some(refMember) - // the rest must be in the same design - case _ if !refMember.isSameOwnerDesignAs(m) => Some(refMember) - case _ => None + // the rest must be in the same design + case refMember if !refMember.isSameOwnerDesignAs(m) => Some(refMember) + case _ => None }.map(m -> _) }.toList val errorMessages = problemReferences.map { (from, to) => @@ -1099,7 +1183,8 @@ final case class DB( domainOwner.getDomainClkConstraintsView.foreach { case constraints.IO(loc = loc: String) => locationMap.get(loc).foreach { prevPort => - locationCollisions += s"${prevPort} and ${domainOwner.getFullName} are both assigned to location `${loc}`" + locationCollisions += + s"${prevPort} and ${domainOwner.getFullName} are both assigned to location `${loc}`" } locationMap += loc -> domainOwner.getFullName foundLoc = true @@ -1121,14 +1206,17 @@ final case class DB( case constraints.IO(bitIdx = None, loc = loc: String) => bitSet.clear() locationMap.get(loc).foreach { prevPort => - locationCollisions += s"${prevPort} and ${port.getFullName} are both assigned to location `${loc}`" + locationCollisions += + s"${prevPort} and ${port.getFullName} are both assigned to location `${loc}`" } locationMap += loc -> port.getFullName if (port.width != 1) - locationCollisions += s"${port.getFullName} has mutliple bits assigned to location `${loc}`" + locationCollisions += + s"${port.getFullName} has mutliple bits assigned to location `${loc}`" case constraints.IO(bitIdx = bitIdx: Int, loc = loc: String) => locationMap.get(loc).foreach { prevPort => - locationCollisions += s"${prevPort} and ${port.getFullName}(${bitIdx}) are both assigned to location `${loc}`" + locationCollisions += + s"${prevPort} and ${port.getFullName}(${bitIdx}) are both assigned to location `${loc}`" } locationMap += loc -> s"${port.getFullName}(${bitIdx})" bitSet -= bitIdx @@ -1176,7 +1264,8 @@ final case class DB( case constraints.IO(dir = dir: Dir) => (dir, port.modifier.dir) match case (Dir.IN, Dir.OUT) | (Dir.OUT, Dir.IN) => - errors += s"${port.getFullName} direction (${port.modifier.dir}) has a resource direction ($dir) mismatch." + errors += + s"${port.getFullName} direction (${port.modifier.dir}) has a resource direction ($dir) mismatch." case _ => case _ => } @@ -1193,7 +1282,6 @@ final case class DB( |Make sure you connect the resource to the port with the correct direction. |""".stripMargin ) - end portResourceDirCheck def check(): Unit = @@ -1248,6 +1336,17 @@ object DB: def fromJsonString(json: String): DB = read[DB](json) end DB +/** Controls how `owner.members(view)` traverses the ownership tree. + * - `Folded`: returns only the direct children of the owner (members whose `getOwner == owner`). + * - `Flattened`: returns all descendants recursively. For a `DFDesignBlock` owner this returns + * every member in the design (via `designMemberTable`). For other owners it recurses into + * nested blocks but does NOT cross `DFDesignBlock` boundaries — sub-designs appear as a single + * opaque entry. + * + * Use `Folded` when you only need to process immediate children (e.g. steps at one nesting level). + * Use `Flattened` when you need to inspect or collect all descendants (e.g. all `Goto` members + * anywhere inside a `ProcessBlock`, or gathering every member to move together with a block). + */ enum MemberView derives CanEqual: case Folded, Flattened diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala index ce140841b..f645fbb32 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFMember.scala @@ -30,6 +30,9 @@ sealed trait DFMember extends Product, Serializable, HasRefCompare[DFMember] der final def getOwnerStepBlock(using MemberGetSet): StepBlock = getOwner match case b: StepBlock => b case o => o.getOwnerStepBlock + final def getOwnerProcessBlock(using MemberGetSet): ProcessBlock = getOwner match + case b: ProcessBlock => b + case o => o.getOwnerProcessBlock final def getOwnerDesign(using MemberGetSet): DFDesignBlock = getOwnerBlock match case d: DFDesignBlock => d @@ -105,7 +108,7 @@ end DFMember object DFMember: given ReadWriter[DFMember] = ReadWriter.merge( - summon[ReadWriter[DFMember.Empty.type]], + summon[ReadWriter[DFMember.Empty]], summon[ReadWriter[DFVal]], summon[ReadWriter[Statement]], summon[ReadWriter[DFInterfaceOwner]], @@ -158,6 +161,12 @@ object DFMember: lazy val getRefs: List[DFRef.TwoWayAny] = Nil def copyWithNewRefs(using RefGen): this.type = this case object Empty extends Empty: + given ReadWriter[Empty] = ReadWriter.merge( + summon[ReadWriter[Empty.type]], + summon[ReadWriter[Goto.ThisStep.type]], + summon[ReadWriter[Goto.NextStep.type]], + summon[ReadWriter[Goto.FirstStep.type]] + ) given ReadWriter[Empty.type] = macroRW sealed trait Named extends DFMember: @@ -214,8 +223,8 @@ sealed trait DFVal extends DFMember.Named: case DFVal.Alias.AsIs(dfType = dfType, relValRef = DFRef(relVal)) if dfType == relVal.dfType => stripAsIsAndDesignParam(relVal) - case DFVal.DesignParam(dfValRef = DFRef(dfVal)) => - stripAsIsAndDesignParam(dfVal) + case dp: DFVal.DesignParam => + stripAsIsAndDesignParam(dp.appliedOrDefaultVal) case _ => dfVal // TODO: maybe we need a better way to check equivalent expressions, with symbolic algebra comparison? // with such comparison, it is possible to simplify expressions at least in the common cases. @@ -442,47 +451,57 @@ object DFVal: final case class DesignParam( dfType: DFType, - dfValRef: DesignParam.Ref, - defaultRef: DesignParam.DefaultRef, + defaultValRef: DesignParam.DefaultValRef, ownerRef: DFOwner.Ref, meta: Meta, tags: DFTags ) extends CanBeExpr derives ReadWriter: assert(!this.isAnonymous, "Design parameters cannot be anonymous.") + // the value will be cached during elaboration, because the reference via the design's paramMap + // will not be available until the design is fully elaborated. during initial contruction and mutation, + // the value will be cached in core.DFVal.DesignParam, and later cleared in core.Design + private var cachedAppliedVal: Option[DFVal] = None + protected[compiler] def appliedValRefOpt(using MemberGetSet): Option[DFDesignBlock.ParamRef] = + getOwnerDesign.paramMap.get(getName) + def appliedValOpt(using MemberGetSet): Option[DFVal] = + if (getSet.isMutable) cachedAppliedVal.orElse(appliedValRefOpt.map(_.get)) + else appliedValRefOpt.map(_.get) + def appliedOrDefaultValRef(using MemberGetSet): DFVal.Ref = + appliedValRefOpt.getOrElse(defaultValRef.asInstanceOf[DFVal.Ref]) + def appliedOrDefaultVal(using MemberGetSet): DFVal = + appliedValOpt.getOrElse(defaultValRef.get.asInstanceOf[DFVal]) + protected[dfhdl] def setCachedAppliedVal(dfVal: DFVal): Unit = cachedAppliedVal = Some(dfVal) + protected[dfhdl] def clearCachedAppliedVal(): Unit = cachedAppliedVal = None protected def protIsFullyAnonymous(using MemberGetSet): Boolean = false protected def protGetConstData(using MemberGetSet): Option[Any] = - dfValRef.get.getConstData + appliedOrDefaultVal.getConstData protected def `prot_=~`(that: DFMember)(using MemberGetSet): Boolean = that match case that: DesignParam => // design parameters are considered to be the same even if they are referencing // a different member (this should be quite common), because that member is // external to the design. however, different default value is considered to be a // different design parameter. - this.dfType =~ that.dfType && this.defaultRef =~ that.defaultRef && + this.dfType =~ that.dfType && this.defaultValRef =~ that.defaultValRef && this.meta =~ that.meta && this.tags =~ that.tags case _ => false protected[ir] def protIsSimilarTo(that: CanBeExpr)(using MemberGetSet): Boolean = that match - case that: DesignParam => - this.dfType.isSimilarTo(that.dfType) && - this.dfValRef.get.isSimilarTo(that.dfValRef.get) - case _ => false + case that: DesignParam => this.dfType.isSimilarTo(that.dfType) + case _ => false protected def setMeta(meta: Meta): this.type = copy(meta = meta).asInstanceOf[this.type] protected def setTags(tags: DFTags): this.type = copy(tags = tags).asInstanceOf[this.type] lazy val getRefs: List[DFRef.TwoWayAny] = - dfValRef :: defaultRef :: dfType.getRefs ++ meta.getRefs + defaultValRef :: dfType.getRefs ++ meta.getRefs def updateDFType(dfType: DFType): this.type = copy(dfType = dfType).asInstanceOf[this.type] def copyWithNewRefs(using RefGen): this.type = copy( meta = meta.copyWithNewRefs, dfType = dfType.copyWithNewRefs, ownerRef = ownerRef.copyAsNewRef, - dfValRef = dfValRef.copyAsNewRef, - defaultRef = defaultRef.copyAsNewRef + defaultValRef = defaultValRef.copyAsNewRef ).asInstanceOf[this.type] end DesignParam object DesignParam: - type Ref = DFRef.TwoWay[DFVal, DesignParam] - type DefaultRef = DFRef.TwoWay[DFVal | DFMember.Empty, DesignParam] + type DefaultValRef = DFRef.TwoWay[DFVal | DFMember.Empty, DesignParam] final case class Special( dfType: DFType, @@ -535,11 +554,8 @@ object DFVal: protected def protGetConstData(using MemberGetSet): Option[Any] = None protected def `prot_=~`(that: DFMember)(using MemberGetSet): Boolean = that match case that: Dcl => - val sameInit = - if (this.initRefList.length == that.initRefList.length) - this.initRefList.lazyZip(that.initRefList).forall(_ =~ _) - else false - this.dfType =~ that.dfType && this.modifier == that.modifier && sameInit && + this.dfType =~ that.dfType && this.modifier == that.modifier && + this.initRefList =~ that.initRefList && this.meta =~ that.meta && this.tags =~ that.tags case _ => false def initList(using MemberGetSet): List[DFVal] = initRefList.map(_.get) @@ -585,9 +601,7 @@ object DFVal: else Some(calcFuncData(dfType, op, argTypes, argData)) protected def `prot_=~`(that: DFMember)(using MemberGetSet): Boolean = that match case that: Func => - this.dfType =~ that.dfType && this.op == that.op && (this.args - .lazyZip(that.args) - .forall((l, r) => l =~ r)) && + this.dfType =~ that.dfType && this.op == that.op && this.args =~ that.args && this.meta =~ that.meta && this.tags =~ that.tags case _ => false // TODO: consider algebraic equivalence be added here @@ -657,7 +671,7 @@ object DFVal: extension (portByNameSelect: PortByNameSelect) def getPortDcl(using MemberGetSet): DFVal.Dcl = val designInst = portByNameSelect.designInstRef.get - getSet.designDB.portsByName(designInst)(portByNameSelect.portNamePath) + getSet.designDB.dupPortsByName(designInst)(portByNameSelect.portNamePath) sealed trait Alias extends CanBeExpr: val relValRef: Alias.Ref @@ -812,11 +826,13 @@ object DFVal: protected def setMeta(meta: Meta): this.type = copy(meta = meta).asInstanceOf[this.type] protected def setTags(tags: DFTags): this.type = copy(tags = tags).asInstanceOf[this.type] override lazy val getRefs: List[DFRef.TwoWayAny] = - dfType.getRefs ++ meta.getRefs ++ List(relValRef) ++ (idxHighRef match - case ref: DFRef.TypeRef => List(ref); - case _ => Nil) ++ (idxLowRef match - case ref: DFRef.TypeRef => List(ref); - case _ => Nil) + dfType.getRefs ++ meta.getRefs ++ List(relValRef) ++ + (idxHighRef match + case ref: DFRef.TypeRef => List(ref); + case _ => Nil) ++ + (idxLowRef match + case ref: DFRef.TypeRef => List(ref); + case _ => Nil) def updateDFType(dfType: DFType): this.type = this def copyWithoutGlobalCtx: this.type = copy().asInstanceOf[this.type] def copyWithNewRefs(using RefGen): this.type = copy( @@ -950,7 +966,8 @@ final case class DFRange( ) extends DFMember derives ReadWriter: protected def `prot_=~`(that: DFMember)(using MemberGetSet): Boolean = that match case that: DFRange => - this.startRef =~ that.startRef && this.endRef =~ that.endRef && this.stepRef =~ that.stepRef && + this.startRef =~ that.startRef && this.endRef =~ that.endRef && + this.stepRef =~ that.stepRef && this.op == that.op && this.meta =~ that.meta && this.tags =~ that.tags case _ => false @@ -1077,9 +1094,10 @@ object StepBlock: extension (stepBlock: StepBlock) def isOnEntry(using MemberGetSet): Boolean = stepBlock.getName == "onEntry" def isOnExit(using MemberGetSet): Boolean = stepBlock.getName == "onExit" + def isFallThrough(using MemberGetSet): Boolean = stepBlock.getName == "fallThrough" def isRegular(using MemberGetSet): Boolean = stepBlock.getName match - case "onEntry" | "onExit" => false - case _ => true + case "onEntry" | "onExit" | "fallThrough" => false + case _ => true final case class Goto( stepRef: Goto.Ref, @@ -1104,8 +1122,11 @@ end Goto object Goto: case object ThisStep extends DFMember.Empty + given ReadWriter[ThisStep.type] = macroRW case object NextStep extends DFMember.Empty + given ReadWriter[NextStep.type] = macroRW case object FirstStep extends DFMember.Empty + given ReadWriter[FirstStep.type] = macroRW type Ref = DFRef.TwoWay[StepBlock | ThisStep.type | NextStep.type | FirstStep.type, Goto] sealed trait DFOwner extends DFMember: @@ -1189,7 +1210,7 @@ object ProcessBlock: def copyWithNewRefs(using RefGen): this.type = this final case class List(refs: scala.List[DFVal.Ref]) extends Sensitivity: protected def `prot_=~`(that: Sensitivity)(using MemberGetSet): Boolean = that match - case that: List => this.refs.lazyZip(that.refs).forall(_ =~ _) + case that: List => this.refs =~ that.refs case _ => false lazy val getRefs: scala.List[DFRef.TwoWayAny] = refs def copyWithNewRefs(using RefGen): this.type = @@ -1288,9 +1309,8 @@ object DFConditional: final case class Alternative(list: List[Pattern]) extends Pattern: protected def `prot_=~`(that: Pattern)(using MemberGetSet): Boolean = that match - case that: Alternative => - this.list.lazyZip(that.list).forall(_ =~ _) - case _ => false + case that: Alternative => this.list =~ that.list + case _ => false lazy val getRefs: List[DFRef.TwoWayAny] = list.flatMap(_.getRefs) def copyWithNewRefs(using RefGen): this.type = copy( list.map(_.copyWithNewRefs) @@ -1298,11 +1318,8 @@ object DFConditional: final case class Struct(name: String, fieldPatterns: List[Pattern]) extends Pattern: protected def `prot_=~`(that: Pattern)(using MemberGetSet): Boolean = that match - case that: Struct => - this.name == that.name && this.fieldPatterns - .lazyZip(that.fieldPatterns) - .forall(_ =~ _) - case _ => false + case that: Struct => this.name == that.name && this.fieldPatterns =~ that.fieldPatterns + case _ => false lazy val getRefs: List[DFRef.TwoWayAny] = fieldPatterns.flatMap(_.getRefs) def copyWithNewRefs(using RefGen): this.type = copy( fieldPatterns = fieldPatterns.map(_.copyWithNewRefs) @@ -1338,9 +1355,7 @@ object DFConditional: protected def `prot_=~`(that: Pattern)(using MemberGetSet): Boolean = that match case that: BindSI => - this.op == that.op && this.parts == that.parts && this.refs - .lazyZip(that.refs) - .forall(_ =~ _) + this.op == that.op && this.parts == that.parts && this.refs =~ that.refs case _ => false lazy val getRefs: List[DFRef.TwoWayAny] = refs def copyWithNewRefs(using RefGen): this.type = copy( @@ -1411,6 +1426,7 @@ end DFConditional object DFLoop: sealed trait Block extends DFBlock derives ReadWriter: def isCombinational(using MemberGetSet): Boolean = this.hasTagOf[CombinationalTag] + def isFallThrough(using MemberGetSet): Boolean = this.hasTagOf[FallThroughTag] final case class DFForBlock( iteratorRef: DFForBlock.IteratorRef, rangeRef: DFForBlock.RangeRef, @@ -1465,6 +1481,7 @@ final case class DFDesignBlock( domainType: DomainType, dclMeta: Meta, instMode: DFDesignBlock.InstMode, + paramMap: ListMap[String, DFDesignBlock.ParamRef], ownerRef: DFOwner.Ref, meta: Meta, tags: DFTags @@ -1476,11 +1493,13 @@ final case class DFDesignBlock( this.domainType =~ that.domainType && this.dclMeta =~ that.dclMeta && this.instMode == that.instMode && + this.paramMap =~ that.paramMap && this.meta =~ that.meta && this.tags =~ that.tags case _ => false protected def setMeta(meta: Meta): this.type = copy(meta = meta).asInstanceOf[this.type] protected def setTags(tags: DFTags): this.type = copy(tags = tags).asInstanceOf[this.type] - lazy val getRefs: List[DFRef.TwoWayAny] = domainType.getRefs ++ dclMeta.getRefs + lazy val getRefs: List[DFRef.TwoWayAny] = + domainType.getRefs ++ dclMeta.getRefs ++ paramMap.values.toList /** Whether this design is considered to be a device's top-level design. THIS MAY NOT BE THE TOP * DESIGN, for example if the design is in a simulation. A design is considered to be a device @@ -1495,11 +1514,13 @@ final case class DFDesignBlock( meta = meta.copyWithNewRefs, dclMeta = dclMeta.copyWithNewRefs, domainType = domainType.copyWithNewRefs, + paramMap = paramMap.map((k, v) => k -> v.copyAsNewRef), ownerRef = ownerRef.copyAsNewRef ).asInstanceOf[this.type] end DFDesignBlock object DFDesignBlock: + type ParamRef = DFRef.TwoWay[DFVal, DFDesignBlock] import InstMode.BlackBox.Source enum InstMode derives CanEqual, ReadWriter: case Normal, Def, Simulation @@ -1568,6 +1589,12 @@ final case class DomainBlock( ).asInstanceOf[this.type] end DomainBlock +object DomainBlock: + extension (domainBlock: DomainBlock) + def isDuplicate: Boolean = domainBlock.ownerRef match + case _: DFRef.DuplicationRef => true + case _ => false + // sealed trait Timer extends DFMember.Named // object Timer: // type Ref = DFRef.TwoWay[Timer, DFMember] @@ -1680,8 +1707,7 @@ final case class TextOut( ) extends Statement: protected def `prot_=~`(that: DFMember)(using MemberGetSet): Boolean = that match case that: TextOut => - this.op =~ that.op && this.msgParts == that.msgParts && - this.msgArgs.lazyZip(that.msgArgs).forall(_ =~ _) && + this.op =~ that.op && this.msgParts == that.msgParts && this.msgArgs =~ that.msgArgs && this.meta =~ that.meta && this.tags =~ that.tags case _ => false protected def setMeta(meta: Meta): this.type = copy(meta = meta).asInstanceOf[this.type] @@ -1708,7 +1734,8 @@ object TextOut: case _ => Nil protected def `prot_=~`(that: Op)(using MemberGetSet): Boolean = (this, that) match case (thisAssert: Assert, thatAssert: Assert) => - thisAssert.assertionRef =~ thatAssert.assertionRef && thisAssert.severity == thatAssert.severity + thisAssert.assertionRef =~ thatAssert.assertionRef && + thisAssert.severity == thatAssert.severity case _ => this equals that def copyWithNewRefs(using RefGen): this.type = this match case Assert(assertionRef, severity) => diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala index 091438f1b..dbdd1edfc 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFRef.scala @@ -3,6 +3,7 @@ import scala.annotation.unchecked.uncheckedVariance import dfhdl.internals.hashString import upickle.default.* import scala.collection.mutable +import scala.collection.immutable.ListMap type DFRefAny = DFRef[DFMember] sealed trait DFRef[+M <: DFMember] extends Product, Serializable derives CanEqual: @@ -20,12 +21,19 @@ object DFRef: val id: Int = 0 override def get(using getSet: MemberGetSet): DFMember.Empty = DFMember.Empty sealed trait OneWay[+M <: DFMember] extends DFRef[M]: - final def copyAsNewRef(using refGen: RefGen): this.type = + def copyAsNewRef(using refGen: RefGen): this.type = refGen.genOneWay[M].asInstanceOf[this.type] object OneWay: final case class Gen[M <: DFMember](grpId: (Int, Int), id: Int) extends OneWay[M] case object Empty extends OneWay[DFMember.Empty] with DFRef.Empty + final case class DuplicationRef(owner: DFOwnerNamed) extends OneWay[DFOwnerNamed]: + val grpId: (Int, Int) = (-1, -1) + val id: Int = -1 + override def get(using getSet: MemberGetSet): DFOwnerNamed = owner + override def getOption(using getSet: MemberGetSet): Option[DFOwnerNamed] = Some(owner) + override def copyAsNewRef(using refGen: RefGen): this.type = this + sealed trait TwoWay[+M <: DFMember, +O <: DFMember] extends DFRef[M]: def copyAsNewRef(using refGen: RefGen): this.type = refGen.genTwoWay[M, O].asInstanceOf[this.type] @@ -40,6 +48,16 @@ object DFRef: override def copyAsNewRef(using refGen: RefGen): this.type = refGen.genTypeRef.asInstanceOf[this.type] + extension (list: List[DFRefAny]) + def =~(that: List[DFRefAny])(using MemberGetSet): Boolean = + list.length == that.length && list.lazyZip(that).forall(_ =~ _) + + extension (list: ListMap[String, DFRefAny]) + def =~(that: ListMap[String, DFRefAny])(using MemberGetSet): Boolean = + list.size == that.size && list.lazyZip(that).forall { + case ((k1, v1), (k2, v2)) => k1 == k2 && v1 =~ v2 + } + extension (ref: DFRefAny) def isTypeRef: Boolean = ref match case ref: TypeRef => true @@ -55,6 +73,8 @@ object DFRef: case TypeRef(grpId, id) => s"TR_${grpId._1.toHexString}_${grpId._2.toHexString}_${id}" case TwoWay.Gen(grpId, id) => s"TW_${grpId._1.toHexString}_${grpId._2.toHexString}_${id}" case OneWay.Gen(grpId, id) => s"OW_${grpId._1.toHexString}_${grpId._2.toHexString}_${id}" + case _: DuplicationRef => + throw new IllegalArgumentException("DuplicationRef must never be serialized") , str => if str == "TWE" then TwoWay.Empty.asInstanceOf[T] diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFTags.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFTags.scala index ec2d15bb3..95291920f 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFTags.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/DFTags.scala @@ -14,6 +14,8 @@ case object BindTag extends DFTag type BindTag = BindTag.type case object CombinationalTag extends DFTag type CombinationalTag = CombinationalTag.type +case object FallThroughTag extends DFTag +type FallThroughTag = FallThroughTag.type case class DefaultRTDomainCfgTag(cfg: RTDomainCfg.Explicit) extends DFTag case object ExtendTag extends DFTag type ExtendTag = ExtendTag.type diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/ir/HasRefCompare.scala b/compiler/ir/src/main/scala/dfhdl/compiler/ir/HasRefCompare.scala index 5dfff9183..3750a2d02 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/ir/HasRefCompare.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/ir/HasRefCompare.scala @@ -1,4 +1,5 @@ package dfhdl.compiler.ir +import scala.collection.immutable.ListMap trait HasRefCompare[T <: HasRefCompare[T]]: private var cachedCompare: Option[(T, Boolean)] = None @@ -12,3 +13,13 @@ trait HasRefCompare[T <: HasRefCompare[T]]: protected def `prot_=~`(that: T)(using MemberGetSet): Boolean lazy val getRefs: List[DFRef.TwoWayAny] def copyWithNewRefs(using RefGen): this.type + +object HasRefCompare: + extension [T <: HasRefCompare[T]](list: List[T]) + def =~(that: List[T])(using MemberGetSet): Boolean = + list.length == that.length && list.lazyZip(that).forall(_ =~ _) + extension [T <: HasRefCompare[T]](list: ListMap[String, T]) + def =~(that: ListMap[String, T])(using MemberGetSet): Boolean = + list.size == that.size && list.lazyZip(that).forall { + case ((k1, v1), (k2, v2)) => k1 == k2 && v1 =~ v2 + } diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala index 14a5b9b2a..e1bfae900 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFOwnerPrinter.scala @@ -26,14 +26,20 @@ trait AbstractOwnerPrinter extends AbstractPrinter: // a def design that is anonymous may not be referenced later, // so we need to check if it has an output port that is referenced later case design: DFDesignBlock if design.instMode == InstMode.Def && design.isAnonymous => - design.members(MemberView.Folded).view.reverse.collectFirst { case port @ DclOut() => + // For duplicate designs, ports may not be in the members list but are + // available via dupPortsByName (with DuplicationRef owners). + // For DuplicationRef-backed ports, we check the PBNS read deps instead. + val ports = getSet.designDB.dupPortsByName(design).view.values.collect { + case port @ DclOut() => port + } + val hasOutput = ports.lastOption.map(port => // no dependencies means the output is not read (referenced later), // so we need to print now - port.getReadDeps.isEmpty - } - // no output port means a Unit return that cannot be referenced, - // so we need to print it now - .getOrElse(true) + port.getPortsByNameSelectors.forall(_.getReadDeps.isEmpty) + ) + // no output port means a Unit return that cannot be referenced, + // so we need to print it now + hasOutput.getOrElse(true) // named members case m: DFMember.Named if !m.isAnonymous => true // excluding late (via) connections @@ -116,7 +122,10 @@ trait AbstractOwnerPrinter extends AbstractPrinter: case DFMember.Empty => "" case _ => csDFCaseGuard(caseBlock.guardRef) s"$csDFCaseKeyword${csDFCasePattern(caseBlock.pattern)}$csGuard$csDFCaseSeparator" - def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean): String + // isUnique is true when the selector is a local enum type, enabling `unique case` in + // SystemVerilog to avoid lint warnings. Global enums are excluded because + // their full set of entries is not guaranteed to be covered at every match site. + def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean, isUnique: Boolean): String def csDFMatchEnd: String def csStepBlock(stepBlock: StepBlock): String def csDFForBlock(forBlock: DFLoop.DFForBlock): String @@ -156,7 +165,10 @@ trait AbstractOwnerPrinter extends AbstractPrinter: ch match case mh: DFConditional.DFMatchHeader => val csSelector = mh.selectorRef.refCodeString.applyBrackets() - sn"""|${csDFMatchStatement(csSelector, mh.hasWildcards)} + val isUnique = mh.selectorRef.get.dfType match + case e: DFEnum => !getSet.designDB.getGlobalNamedDFTypes.contains(e) + case _ => false + sn"""|${csDFMatchStatement(csSelector, mh.hasWildcards, isUnique)} |${csChains.hindent} |${csDFMatchEnd}""" case ih: DFConditional.DFIfHeader => csChains @@ -202,14 +214,16 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: s"def ${design.dclName}$designParamCS($defArgsCS)$retTypeCS =\n${bodyWithDcls.hindent}\nend ${design.dclName}" s"${printer.csAnnotations(design.dclMeta.annotations)}$dcl\n" end csDFDesignDefDcl + private def csDesignParamList(design: DFDesignBlock): List[String] = + design.paramMap.view.map { (name, ref) => + s"${name} = ${ref.refCodeString}" + }.toList def csDFDesignDefInst(design: DFDesignBlock): String = - val ports = design.members(MemberView.Folded).view.collect { case port @ DclIn() => + val ports = getSet.designDB.dupPortsByName(design).view.values.collect { case port @ DclIn() => val DFNet.Connection(_, from: DFVal, _) = port.getConnectionTo.get.runtimeChecked printer.csDFValRef(from, design.getOwner) }.mkString(", ") - val designParamList = design.members(MemberView.Folded).collect { case param: DesignParam => - s"${param.getName} = ${param.dfValRef.refCodeString}" - } + val designParamList = csDesignParamList(design) val designParamCS = if (designParamList.length == 0) "" else if (designParamList.length == 1) designParamList.mkString("(", ", ", ")") @@ -219,9 +233,7 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: else s"val ${design.getName} = $dcl" end csDFDesignDefInst def csDFDesignBlockParamInst(design: DFDesignBlock): String = - val designParamList = design.members(MemberView.Folded).collect { case param: DesignParam => - s"${param.getName} = ${param.dfValRef.refCodeString}" - } + val designParamList = csDesignParamList(design) if (designParamList.length <= 1) designParamList.mkString("(", ", ", ")") else "(" + designParamList.mkString("\n", ",\n", "\n").hindent(2) + ")" def csDFDesignBlockDcl(design: DFDesignBlock): String = @@ -247,11 +259,11 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: case _ => "EDDesign" val designParamList = design.members(MemberView.Folded).collect { case param: DesignParam => val defaultValue = - if (design.isTop) s" = ${param.dfValRef.refCodeString}" + if (design.isTop) s" = ${param.appliedOrDefaultValRef.refCodeString}" else - param.defaultRef.get match + param.defaultValRef.get match case DFMember.Empty => "" - case _ => s" = ${param.defaultRef.refCodeString}" + case _ => s" = ${param.defaultValRef.refCodeString}" s"val ${param.getName}${printer.csDFValConstType(param.dfType)}$defaultValue" } val designIsVendorIPBlackbox = design.isVendorIPBlackbox @@ -279,9 +291,7 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: end csDFDesignBlockDcl def csDFDesignBlockInst(design: DFDesignBlock): String = val body = csDFDesignLateBody(design) - val designParamList = design.members(MemberView.Folded).collect { case param: DesignParam => - s"${param.getName} = ${param.dfValRef.refCodeString}" - } + val designParamList = csDesignParamList(design) val designParamCS = // for vendor IP blackbox, we define the parameters in the class extension instead of the // blackbox instantiation @@ -344,7 +354,7 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: def csDFCaseKeyword: String = "case " def csDFCaseSeparator: String = " =>" def csDFMatchEnd: String = "end match" - def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean): String = + def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean, isUnique: Boolean): String = s"$csSelector match" def csProcessBlock(pb: ProcessBlock): String = val body = csDFOwnerBody(pb) @@ -357,28 +367,38 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter: def csStepBlock(stepBlock: StepBlock): String = val body = csDFOwnerBody(stepBlock) val name = stepBlock.getName - val defType = if (stepBlock.isRegular) "Step" else "Unit" - s"def $name: $defType =\n${body.hindent}\nend $name" + val defType = + if (stepBlock.isRegular) ": Step" + else if (stepBlock.isFallThrough) + printer.csDFValType(stepBlock.getVeryLastMember.get.asInstanceOf[DFVal].dfType) + else ": Unit" + s"def $name$defType =\n${body.hindent}\nend $name" def csDFForBlock(forBlock: DFLoop.DFForBlock): String = val csCOMB_LOOP = if (forBlock.isCombinational) "COMB_LOOP" else "" + val csFALL_THROUGH = if (forBlock.isFallThrough) "FALL_THROUGH" else "" val body = sn"""|${csCOMB_LOOP} + |${csFALL_THROUGH} |${csDFOwnerBody(forBlock)}""" val named = forBlock.meta.nameOpt.map(n => s"val $n = ").getOrElse("") + val endName = forBlock.meta.nameOpt.map(n => s"end $n").getOrElse("end for") //format: off sn"""|${named}for (${forBlock.iteratorRef.refCodeString} <- ${printer.csDFRange(forBlock.rangeRef.get)}) |${body.hindent} - |end for""" + |$endName""" //format: on def csDFWhileBlock(whileBlock: DFLoop.DFWhileBlock): String = val csCOMB_LOOP = if (whileBlock.isCombinational) "COMB_LOOP" else "" + val csFALL_THROUGH = if (whileBlock.isFallThrough) "FALL_THROUGH" else "" val body = sn"""|${csCOMB_LOOP} + |${csFALL_THROUGH} |${csDFOwnerBody(whileBlock)}""" val named = whileBlock.meta.nameOpt.map(n => s"val $n = ").getOrElse("") + val endName = whileBlock.meta.nameOpt.map(n => s"end $n").getOrElse("end while") sn"""|${named}while (${whileBlock.guardRef.refCodeString}) |${body.hindent} - |end while""" + |$endName""" def csDomainBlock(domain: DomainBlock): String = val body = csDFOwnerBody(domain) val named = domain.meta.nameOpt.map(n => s"val $n = ").getOrElse("") diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala index e8e988d4a..59681dc47 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/DFValPrinter.scala @@ -168,7 +168,7 @@ trait AbstractValPrinter extends AbstractPrinter: case dv: Const => csDFValConstExpr(dv) case dv: Func => csDFValFuncExpr(dv, typeCS) case dv: Alias => csDFValAliasExpr(dv) - case dv: DFVal.DesignParam => dv.dfValRef.refCodeString + case dv: DFVal.DesignParam => dv.appliedValRefOpt.get.refCodeString case dv: DFConditional.Header => printer.csDFConditional(dv) // case dv: Timer.IsActive => csTimerIsActive(dv) case dv: Special => diff --git a/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala index a40727937..a82669390 100644 --- a/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala +++ b/compiler/ir/src/main/scala/dfhdl/compiler/printing/Printer.scala @@ -145,6 +145,9 @@ trait Printer end csDFMember def designFileName(designName: String): String def globalFileName: String + protected def hasGlobalContentCheck: Boolean = + getSet.designDB.membersGlobals.exists(!_.isAnonymous) || csGlobalTypeDcls.nonEmpty + lazy val hasGlobalContent: Boolean = hasGlobalContentCheck def csGlobalFileContent: String = sn"""|$csGlobalConstIntDcls |$csGlobalTypeDcls @@ -183,16 +186,20 @@ trait Printer ) ) else None - val globalSourceFile = - SourceFile( - SourceOrigin.Compiled, - SourceType.GlobalDef, - hdlFolderName + separatorChar + globalFileName, - formatCode(csGlobalFileContent, withColor = false) - ) + val globalSourceFile: Option[SourceFile] = + if (hasGlobalContent) + Some( + SourceFile( + SourceOrigin.Compiled, + SourceType.GlobalDef, + hdlFolderName + separatorChar + globalFileName, + formatCode(csGlobalFileContent, withColor = false) + ) + ) + else None val compiledFiles = Iterable( dfhdlSourceFile, - Some(globalSourceFile), + globalSourceFile, designDB.uniqueDesignMemberList.view.map { case (block: DFDesignBlock, _) => val sourceType = block.instMode match case _: DFDesignBlock.InstMode.BlackBox => SourceType.BlackBox diff --git a/compiler/ir/src/main/scala/dfhdl/options/PrinterOptions.scala b/compiler/ir/src/main/scala/dfhdl/options/PrinterOptions.scala index be345ee12..715137eac 100644 --- a/compiler/ir/src/main/scala/dfhdl/options/PrinterOptions.scala +++ b/compiler/ir/src/main/scala/dfhdl/options/PrinterOptions.scala @@ -8,7 +8,8 @@ final case class PrinterOptions( align: Align, color: Color, showGlobals: ShowGlobals, - designPrintFilter: DesignPrintFilter + designPrintFilter: DesignPrintFilter, + globalDefsFileName: GlobalDefsFileName ) object PrinterOptions: opaque type Defaults[-T] <: PrinterOptions = PrinterOptions @@ -17,12 +18,14 @@ object PrinterOptions: align: Align, color: Color, showGlobals: ShowGlobals, - designPrintFilter: DesignPrintFilter + designPrintFilter: DesignPrintFilter, + globalDefsFileName: GlobalDefsFileName ): Defaults[Any] = PrinterOptions( align = align, color = color, showGlobals = showGlobals, - designPrintFilter = designPrintFilter + designPrintFilter = designPrintFilter, + globalDefsFileName = globalDefsFileName ) given (using defaults: Defaults[Any]): PrinterOptions = defaults into opaque type Align <: Boolean = Boolean @@ -41,6 +44,11 @@ object PrinterOptions: given ShowGlobals = false given Conversion[Boolean, ShowGlobals] = identity + into opaque type GlobalDefsFileName <: String = String + object GlobalDefsFileName: + given GlobalDefsFileName = "" + given Conversion[String, GlobalDefsFileName] = identity + trait DesignPrintFilter: def apply(design: ir.DFDesignBlock): Boolean diff --git a/compiler/stages/src/main/resources/dfhdl_defs.vh b/compiler/stages/src/main/resources/dfhdl_defs.vh index a561bc783..a1b9ace64 100644 --- a/compiler/stages/src/main/resources/dfhdl_defs.vh +++ b/compiler/stages/src/main/resources/dfhdl_defs.vh @@ -23,9 +23,7 @@ // OTHER DEALINGS IN THE SOFTWARE. // // For more information, please refer to -// -// TODO: remove UNUSEDSIGNAL lint-off after -// https://github.com/verilator/verilator/issues/6893 is fixed + `define MAX(a, b) ((a) > (b) ? (a) : (b)) `define MIN(a, b) ((a) < (b) ? (a) : (b)) `define ABS(a) ((a) < 0 ? -(a) : (a)) @@ -75,7 +73,6 @@ `define SIGNED_SHIFT_RIGHT(data, shift, width) \ ((data[width-1] == 1'b1) ? ((data >> shift) | ({width{1'b1}} << (width - shift))) : (data >> shift)) function integer clog2; -/* verilator lint_off UNUSEDSIGNAL */ input integer n; integer result, temp; begin @@ -86,12 +83,10 @@ begin result = result + 1; end clog2 = result; -/* verilator lint_on UNUSEDSIGNAL */ end endfunction // Function to perform base raised to the power of exp (base ** exp) function integer power; -/* verilator lint_off UNUSEDSIGNAL */ input integer base; input integer exp; integer i; // Loop variable @@ -104,7 +99,6 @@ begin power = power * base; end end -/* verilator lint_on UNUSEDSIGNAL */ end endfunction diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ConnectUnused.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ConnectUnused.scala index 3a522c939..d7f13de65 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ConnectUnused.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ConnectUnused.scala @@ -14,27 +14,23 @@ case object ConnectUnused extends Stage: def nullifies: Set[Stage] = Set() def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = given RefGen = RefGen.fromGetSet - val patchList: List[(DFMember, Patch)] = designDB.designMemberList.collect { - // Find all design instances (internal designs) - case (designInst: DFDesignBlock, members) if !designInst.isTop => + val patchList: List[(DFMember, Patch)] = designDB.dupPortsByName.view.collect { + // For design instances + case (designInst, ports) if !designInst.isTop => val designInstPatches = mutable.ListBuffer.empty[(DFMember, Patch)] - // Get all ports for this design instance - val ports = members.view.collect { - case p: DFVal.Dcl if p.isPort => p - } // Find ports annotated with @unused - val unusedPorts = ports.filter { port => + val unusedPorts = ports.view.values.filter { port => port.meta.annotations.exists { case _: annotation.Unused => true case _ => false } - } + }.toList // Create connections to OPEN for unused ports val dsn = new MetaDesign(designInst, Patch.Add.Config.After): for (unusedPort <- unusedPorts) do unusedPort.asDclAny <> OPEN dsn.patch - } + }.toList designDB.patch(patchList) end transform end ConnectUnused diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropDesignParamDeps.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropDesignParamDeps.scala index 3fce6aa6e..a6bf77293 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropDesignParamDeps.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropDesignParamDeps.scala @@ -39,7 +39,7 @@ case object DropDesignParamDeps extends Stage: // Collect all design parameters that have dependencies on other design parameters designDB.members.foreach { case param: DFVal.DesignParam => - param.defaultRef.get match + param.defaultValRef.get match case default: DFVal => if (hasDesignParamDependency(default)) designParamDefaultsToInline.add(default) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropLocalDcls.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropLocalDcls.scala index 982856b52..17638d2d2 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropLocalDcls.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropLocalDcls.scala @@ -5,11 +5,79 @@ import dfhdl.compiler.ir.* import dfhdl.compiler.patching.* import dfhdl.options.CompilerOptions -/** This stage moves the local vars or named constants (at the conditional/process block level) to - * either the design level (in Verilog) or its owner level (in VHDL), where the owner can be a - * design or a process. Verilog does not support declarations inside an always block, so they must - * be moved to the design level. VHDL does support declarations at the process level. +//format: off +/** This stage moves local variable and constant declarations out of their lexical scope to a + * position where the target language supports them. It also inserts reset-to-init assignments for + * local variables with initialization values inside RT process blocks. + * + * ==Context== + * + * Verilog `always` blocks (process blocks) do not support variable declarations — all declarations + * must appear at the design (module) level. VHDL `process` blocks DO support variable + * declarations, so only conditional-block-level declarations need lifting in VHDL. + * + * ==Rules== + * + * ===Rule 1: Declarations inside conditional blocks=== + * Any local variable (`VAR`) or non-global named constant (`CONST`) that is declared inside a + * conditional block (`if`, `match`, or `while`) is moved to before the top-level conditional + * header that contains it: + * - For non-VHDL backends: if the top-level conditional is itself inside a process block, the + * declaration is moved to before the process block (design level). + * - For VHDL: the declaration is moved to just before the top-level conditional header, staying + * inside the process block if one exists. + * {{{ + * // Before — zz declared inside an if inside a process + * class ID extends EDDesign: + * process(all): + * if (x > 5) + * val zz = SInt(16) <> VAR + * ... + * + * // After (Verilog) — moved to design level, before the process + * class ID extends EDDesign: + * val zz = SInt(16) <> VAR + * process(all): + * if (x > 5) + * ... + * + * // After (VHDL) — moved to just before the if, inside the process + * class ID extends EDDesign: + * process(all): + * val zz = SInt(16) <> VAR + * if (x > 5) + * ... + * }}} + * + * ===Rule 2: Declarations directly inside process blocks=== + * A local variable declared directly inside a process block (not inside a conditional): + * - For non-VHDL backends: moved to before the process block (design level). + * - For VHDL: moved to the top of the process block (before any statements), because VHDL + * requires all variable declarations to precede statements in a process. + * {{{ + * // Before + * class ID extends EDDesign: + * process(all): + * stmt1 + * val zz = SInt(16) <> VAR + * stmt2 + * + * // After (Verilog) — moved to design level + * class ID extends EDDesign: + * val zz = SInt(16) <> VAR + * process(all): + * stmt1 + * stmt2 + * + * // After (VHDL) — moved to top of process + * class ID extends EDDesign: + * process(all): + * val zz = SInt(16) <> VAR + * stmt1 + * stmt2 + * }}} */ +//format: on case object DropLocalDcls extends Stage: override def dependencies: List[Stage] = List(ExplicitNamedVars) override def nullifies: Set[Stage] = Set() diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala index 47019d6f8..681c59046 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTProcess.scala @@ -11,11 +11,179 @@ import scala.collection.immutable.ListMap import dfhdl.core.DFType.asFE import dfhdl.core.{DFValAny, DFOwnerAny, asValAny, DFC} //format: off -/** This stage drops RT processes by creating an explicit enumerated state machines +/** This stage drops RT processes by creating an explicit enumerated state machine. + * + * Each RT `process` block containing `StepBlock` definitions (produced by the preceding + * `FlattenStepBlocks` stage) is replaced by: + * - An `enum` type whose entries correspond 1-to-1 with the step names. + * - A `VAR.REG` state register of that enum type, initialised to the first step. + * - A `match` expression that dispatches on the current state value and contains one + * case per step. + * + * All `Goto` members are already explicit `StepBlock` references at this point + * (relative forms — `NextStep`, `ThisStep`, `FirstStep` — were resolved by + * `FlattenStepBlocks`). Each `Goto` is replaced by an assignment to `stateReg.din`. + * + * ==Rules== + * + * ===Rule 1: Basic FSM transformation=== + * Each step becomes a match case; each `Goto` inside it becomes `stateReg.din := EnumEntry`. + * {{{ + * // Before + * process: + * def S0: Step = + * y.din := 0 + * if (x) S1 else S0 + * def S1: Step = + * y.din := 1 + * if (x) S2 else S0 + * def S2: Step = + * y.din := 0 + * if (x) S2 else S0 + * + * // After + * enum State(...) extends Encoded.Manual(2): + * case S0 extends State(d"2'0") + * case S1 extends State(d"2'1") + * case S2 extends State(d"2'2") + * val state = State <> VAR.REG init State.S0 + * state match + * case State.S0 => + * y.din := 0 + * if (x) state.din := State.S1 + * else state.din := State.S0 + * case State.S1 => + * y.din := 1 + * if (x) state.din := State.S2 + * else state.din := State.S0 + * case State.S2 => + * y.din := 0 + * if (x) state.din := State.S2 + * else state.din := State.S0 + * end match + * }}} + * + * ===Rule 2: `onEntry` block inlining=== + * If a step `S` has an `onEntry` child block, its body is inlined at every site that + * transitions *into* `S` from a *different* step (i.e., wherever a `Goto` targets `S` + * and the source step differs from `S`), immediately before the `stateReg.din` assignment. + * The `onEntry` block itself is removed from the case body. Self-transitions (`S -> S`) + * do NOT trigger `onEntry`. + * {{{ + * // Before + * def S0: Step = + * if (x) S1 else S0 + * def S1: Step = + * def onEntry = + * y.din := 1 // run only when entering S1 from a different step + * if (x) S1 else S0 // self-transition: onEntry NOT fired + * + * // After + * case State.S0 => + * if (x) + * y.din := 1 // S1.onEntry inlined (S0 -> S1: different step) + * state.din := State.S1 + * else state.din := State.S0 + * case State.S1 => + * if (x) state.din := State.S1 // S1 -> S1: onEntry NOT inlined + * else state.din := State.S0 + * }}} + * + * ===Rule 3: `onExit` block inlining=== + * If a step `S` has an `onExit` child block, its body is inlined at every site that + * transitions *out of* `S` to a *different* step, immediately before any `onEntry` + * inlining and the `stateReg.din` assignment. Self-transitions do not trigger `onExit`. + * {{{ + * // Before + * def S2: Step = + * def onExit = + * y.din := 0 // run whenever we leave S2 to another step + * if (x) S2 else S0 + * + * // After + * case State.S2 => + * if (x) state.din := State.S2 // self-loop: onExit NOT triggered + * else + * y.din := 0 // S2.onExit inlined here (transition to S0) + * state.din := State.S0 + * end if + * }}} + * + * ===Rule 4: `fallThrough` block — conditional cascading=== + * If a step `S` has a `fallThrough` child block, it defines a condition under which + * control *immediately falls through* to the next step in the same clock cycle (without + * waiting). When transitioning into `S`: + * 1. Inline `S.onEntry` (if any) and assign `stateReg.din := S`. + * 2. Emit a conditional `if (fallThroughCondition)` block. + * 3. Inside that block, recursively apply steps 1–2 for the next step (the one `S` + * itself eventually transitions to via its own `Goto`). + * The `fallThrough` condition represents the *exit* predicate — when true, control passes + * to the subsequent step without registering in `S`. + * {{{ + * // Before + * def S0: Step = + * y.din := 0 + * S1 + * def S1: Step = + * def fallThrough = x // if x, skip straight to S2 + * def onEntry = y.din := 1 + * S2 + * def S2: Step = + * def fallThrough = !x // if !x, skip straight to S3 + * def onEntry = y.din := !y + * S3 + * + * // After (case for S0) + * case State.S0 => + * y.din := 0 + * y.din := 1 // S1.onEntry + * state.din := State.S1 + * if (x) // S1.fallThrough: skip to S2 + * y.din := !y // S2.onEntry + * state.din := State.S2 + * if (!x) // S2.fallThrough: skip to S3 + * ... + * end if + * end if + * }}} + * + * ===Rule 5: Circular fall-through protection=== + * The recursive `fallThrough` chain stops as soon as the next step to handle equals the + * step that contains the original `Goto`. This prevents infinite expansion when the fall- + * through cycle loops back to the current case. + * {{{ + * // Before + * def S0: Step = + * def fallThrough = x + * def onEntry = y.din := y + * S1 + * def S1: Step = + * def fallThrough = !x + * def onEntry = y.din := !y + * S2 + * def S2: Step = + * def fallThrough = x ^ x.reg(1, init = 0) + * def onEntry = y.din := y ^ y.reg + * S0 + * + * // After (case for S2) + * case State.S2 => + * y.din := y // S0.onEntry + * state.din := State.S0 + * if (x) // S0.fallThrough: cascade to S1 + * y.din := !y // S1.onEntry + * state.din := State.S1 + * if (!x) // S1.fallThrough: cascade to S2 + * y.din := y ^ y.reg // S2.onEntry + * state.din := State.S2 + * // S2.fallThrough would cascade to S0, but S0 == currentStep => stop + * end if + * end if + * }}} */ //format: on case object DropRTProcess extends Stage: - def dependencies: List[Stage] = List() // DropRTWaits + def dependencies: List[Stage] = List(FlattenStepBlocks) def nullifies: Set[Stage] = Set(DFHDLUniqueNames) def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = @@ -37,7 +205,7 @@ case object DropRTProcess extends Stage: val entries = ListMap.from(stateBlocks.view.zipWithIndex.map { case (sb, idx) => sb.getName -> BigInt(idx) }) - val stateEnumIR = DFEnum(enumName, stateBlocks.length.bitsWidth(false), entries) + val stateEnumIR = DFEnum(enumName, (stateBlocks.length - 1).bitsWidth(false), entries) type StateEnum = dfhdl.core.DFEnum[dfhdl.core.DFEncoding] val stateEnumFE = stateEnumIR.asFE[StateEnum] def enumEntry(value: BigInt)(using DFC) = dfhdl.core.DFVal.Const(stateEnumFE, Some(value)) @@ -65,43 +233,75 @@ case object DropRTProcess extends Stage: ) prevBlockOrHeader = block } - val removedOnEntryExitMembers = mutable.Set.empty[DFMember] + val removedOnEntryExitFallThroughMembers = mutable.Set.empty[DFMember] val gotoDsns = gotos.map { g => new MetaDesign( g, Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.ChangeRefAndRemove), dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) ): + import dfhdl.core.{DFIf, DFUnit, DFBool} val currentStepBlock = g.getOwnerStepBlock - val nextStepBlock: StepBlock = g.stepRef.get match - case stepBlock: StepBlock => stepBlock - case Goto.ThisStep => currentStepBlock - case Goto.NextStep => nextBlocks(currentStepBlock) - case Goto.FirstStep => stateBlocks.head + // the stage `FlattenStepBlocks` guarantees that the goto always references a regular StepBlock + val nextStepBlock: StepBlock = g.stepRef.get.asInstanceOf[StepBlock] + def setState(nextStepBlock: StepBlock): Unit = + stateReg.din.:=(enumEntry(entries(nextStepBlock.getName)))(using + dfc.setMeta(g.meta) + ) if (currentStepBlock != nextStepBlock) - // add currentStepBlock-onExit and nextStepBlock onEntry members + // add currentStepBlock-onExit members currentStepBlock.members(MemberView.Folded).collectFirst { case onExit: StepBlock if onExit.isOnExit => onExit }.foreach { onExit => val onExitMembers = onExit.members(MemberView.Flattened) plantClonedMembers(onExit, onExitMembers) - removedOnEntryExitMembers += onExit - removedOnEntryExitMembers ++= onExitMembers - } - nextStepBlock.members(MemberView.Folded).collectFirst { - case onEntry: StepBlock if onEntry.isOnEntry => onEntry - }.foreach { onEntry => - val onEntryMembers = onEntry.members(MemberView.Flattened) - plantClonedMembers(onEntry, onEntryMembers) - removedOnEntryExitMembers += onEntry - removedOnEntryExitMembers ++= onEntryMembers + removedOnEntryExitFallThroughMembers += onExit + removedOnEntryExitFallThroughMembers ++= onExitMembers } + def handleNextStep(nextStepBlock: StepBlock): Unit = + // onEntry members will always activated, even if we fall-through the next step block + val nextStepBlockMembers = nextStepBlock.members(MemberView.Flattened) + nextStepBlockMembers.collectFirst { + case onEntry: StepBlock if onEntry.isOnEntry => onEntry + }.foreach { onEntry => + val onEntryMembers = onEntry.members(MemberView.Flattened) + plantClonedMembers(onEntry, onEntryMembers) + removedOnEntryExitFallThroughMembers += onEntry + removedOnEntryExitFallThroughMembers ++= onEntryMembers + } + setState(nextStepBlock) + // in case of circular fall-through, we stop + if (nextStepBlock != currentStepBlock) + val fallThroughCond = nextStepBlockMembers.collectFirst { + case fallThrough: StepBlock if fallThrough.isFallThrough => + val fallThroughMembers = fallThrough.members(MemberView.Flattened) + val fallThroughDFC = dfc.setMeta(fallThrough.meta.anonymize) + removedOnEntryExitFallThroughMembers += fallThrough + removedOnEntryExitFallThroughMembers ++= fallThroughMembers + // planting all members except the last one (the ident of the condition) + val clonedMemberMap = + plantClonedMembers(fallThrough, fallThroughMembers.dropRight(1)) + val Ident(origCond) = fallThroughMembers.last.runtimeChecked + val cond = clonedMemberMap.getOrElse(origCond, origCond).asInstanceOf[DFVal] + val ifBlock = DFIf.Block( + Some(cond.asValOf[DFBool]), + DFIf.Header(DFUnit)(using fallThroughDFC) + )(using fallThroughDFC) + dfc.enterOwner(ifBlock) + val fallThroughStepBlock = nextBlocks(nextStepBlock) + handleNextStep(fallThroughStepBlock) + dfc.exitOwner() + } + end if + end handleNextStep + handleNextStep(nextStepBlock) + else + setState(nextStepBlock) end if - stateReg.din.:=(enumEntry(entries(nextStepBlock.getName)))(using dfc.setMeta(g.meta)) } Iterator( List(dsn.patch, pbPatch), - removedOnEntryExitMembers.map(_ -> Patch.Remove()), + removedOnEntryExitFallThroughMembers.map(_ -> Patch.Remove()), caseDsns.map(_.patch), gotoDsns.map(_.patch) ).flatten diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala index ebcbee597..70de538a2 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropRTWaits.scala @@ -17,32 +17,140 @@ import scala.collection.mutable * end S_N * ``` * where N is the step number. - * 2. Multiple single cycle waits are replaced with sequential step definitions (S_1, S_2, S_3, ...) + * 2. Multiple single cycle waits are replaced with sequential step definitions (S_0, S_1, S_2, ...) * ```scala + * def S_0: Step = + * NextStep + * end S_0 * def S_1: Step = * NextStep * end S_1 * def S_2: Step = * NextStep * end S_2 - * def S_3: Step = - * NextStep - * end S_3 * ``` - * where 1, 2, 3 are the step numbers. + * where 0, 1, 2 are the step numbers. * 3. While loop `while (condition) { body }` is replaced with: * ```scala * def S_N: Step = - * if (condition) { + * if (condition) + * body + * ThisStep + * else + * NextStep + * end if + * end S_N + * ``` + * where N is the step number. + * 4. While loops that are tagged with `FALL_THROUGH` are replaced with: + * ```scala + * def S_N: Step = + * def fallThrough = !condition + * if (condition) * body * ThisStep - * } else { + * else * NextStep - * } + * end if * end S_N * ``` - * 4. While loops with nested waits: waits inside while loops become step definitions with nested naming - * (S_1_1, S_1_2, etc.), and the while loop itself becomes a step definition + * where N is the step number. + * 5. While loops with nested waits: waits inside while loops become step definitions with nested naming + * (S_0_0, S_0_1, etc.), and the while loop itself becomes a step definition (S_0) + * 6. If the process does not start with a step, a wait statement or while loop, we add an empty step definition for the first + * step (with a NextStep return value) before the first member. Internally, there could be anonymous value + * members before the step/while/wait members, but these are ignored for the check of what the first member is. + * For example: + * ```scala + * process: + * x.din := 0 + * end process + * ``` + * will be transformed to: + * ```scala + * process: + * def S_0: Step = + * NextStep + * end S_0 + * x.din := 0 + * end process + * ``` + * 7. The user can define explicit naming by setting a value name for a wait statement or a while block, + * or by defining a step block. Nested steps are named by appending the parent step name and an underscore, + * unless the nested step already includes the parent step name with an underscore. + * Unnamed steps are enumerated with the parent step name and an underscore. The enumeration starts from 0, + * and increments while counting the named steps as well. + * For example: + * ```scala + * process: + * def MyStep: Step = + * 1.cy.wait //no name, so enumerate as MyStep_0 + * val MyWait = 1.cy.wait //named, so use as MyStep_MyWait + * 1.cy.wait //no name, so enumerate as MyStep_2 + * def Internal: Step = //nested step, so change name to MyStep_Internal + * NextStep + * end Internal + * def MyStep_Internal2: Step = //nested step, but already includes the parent step name with an underscore, + * NextStep //so use as MyStep_Internal2 + * end MyStep_Internal2 + * NextStep + * end MyStep + * x.din := y + * val MyStepB = 1.cy.wait //named, so use as MyStepB + * x.din := 1 + * 1.cy.wait //no name, so enumerate as MyStep_2 + * val MyWhile = while (y) //named, so use as MyWhile + * x.din := 1 + * def GoGo: Step = //nested step, so change name to MyWhile_GoGo + * NextStep + * end GoGo + * end MyWhile + * x.din := 1 + * end process + * ``` + * will be transformed to: + * ```scala + * process: + * def MyStep: Step = + * def MyStep_0: Step = + * NextStep + * end MyStep_0 + * def MyStep_MyWait: Step = + * NextStep + * end MyStep_MyWait + * def MyStep_2: Step = + * NextStep + * end MyStep_2 + * def MyStep_Internal: Step = + * NextStep + * end MyStep_Internal + * def MyStep_Internal2: Step = + * NextStep + * end MyStep_Internal2 + * NextStep + * end MyStep + * x.din := y + * def MyStepB: Step = + * NextStep + * end MyStepB + * x.din := 1 + * def S_2: Step = + * NextStep + * end S_2 + * def MyWhile: Step = + * if (y) + * x.din := 1 + * def MyWhile_GoGo: Step = + * NextStep + * end MyWhile_GoGo + * ThisStep + * else + * NextStep + * end if + * end MyWhile + * x.din := 1 + * end process + * ``` */ //format: on case object DropRTWaits extends Stage: @@ -55,41 +163,75 @@ case object DropRTWaits extends Stage: // each process block has its own step enumeration case pb: ProcessBlock if pb.isInRTDomain => val pbMembers = pb.members(MemberView.Flattened) - // the nested step number is stored in a stack, the head of the list is the current step number - var stepNumberNest = List(1) + // Rule 7: step scope (prefix, counter). + case class StepScope(prefix: String, counter: Int) + var stepNameStack = List(StepScope("", 0)) // for nested while loops, we need to keep track of the exit members. // an exit member may have multiple patches that need to be applied in the LIFO order they are stacked. var exitMemberPatches = mutable.Map.empty[DFMember, List[Patch]] - // the step number is incremented by 1 + // increment the counter for the current (top) step scope def nextStepBlock(): Unit = - stepNumberNest = (stepNumberNest.head + 1) :: stepNumberNest.tail - // entering a step block starts a deeper nesting level and memoizes an exit member patch that - // needs to be applied - def enterStepBlock(patch: (DFMember, Patch)): Unit = + val h = stepNameStack.head + stepNameStack = StepScope(h.prefix, h.counter + 1) :: stepNameStack.tail + // entering a step block (e.g. while body) starts a deeper nesting level and memoizes an exit member patch + def enterStepBlock(stepMember: DFMember, exitMember: DFMember, patch: Option[Patch]): Unit = // stacking the patches for the exit member - exitMemberPatches.get(patch._1) match - case Some(patches) => exitMemberPatches += patch._1 -> (patch._2 :: patches) - case None => exitMemberPatches += patch._1 -> List(patch._2) - // starting a new step block with a new step number - stepNumberNest = 1 :: stepNumberNest + exitMemberPatches.get(exitMember) match + case Some(patches) => exitMemberPatches += exitMember -> (patch.toList ++ patches) + case None => exitMemberPatches += exitMember -> patch.toList + stepNameStack = StepScope(getStepName(stepMember), 0) :: stepNameStack // checking if the step block has an exit member and returning the patches that need to be applied. def checkAndExitStepBlock(lastMember: DFMember): List[(DFMember, Patch)] = exitMemberPatches.get(lastMember) match case Some(patches) => - stepNumberNest = stepNumberNest.tail + stepNameStack = stepNameStack.tail nextStepBlock() patches.map(lastMember -> _) case None => Nil - def getStepName(): String = - stepNumberNest.view.reverse.mkString("S_", "_", "") + + def getStepName(stepMember: DFMember): String = + val prefix = stepNameStack.head.prefix + stepMember.meta.nameOpt match + case Some(name) => + if (prefix.isEmpty) name + else if (name.startsWith(prefix + "_")) name + else s"${prefix}_${name}" + case None => + if (prefix.isEmpty) s"S_${stepNameStack.head.counter}" + else s"${prefix}_${stepNameStack.head.counter}" + + // Rule 6: If process does not start with step, wait, or while, add empty S_0 before first member + val needsInitialStep = + pbMembers + .dropWhile { + case v: DFVal => v.isAnonymous + case _ => false + }.headOption match + case None => true + case Some(_: Wait) => false + case Some(wb: DFLoop.DFWhileBlock) => wb.isCombinational + case Some(_: StepBlock) => false + case _ => true + + val initialStepPatches = if needsInitialStep then + val stepName = "S_0" + nextStepBlock() + val dsn = new MetaDesign(pb, Patch.Add.Config.InsideFirst): + import dfhdl.core.StepBlock + val step = StepBlock.forced(using dfc.setName(stepName)) + dfc.enterOwner(step) + NextStep + dfc.exitOwner() + List(dsn.patch) + else Nil // transforming the process block members. // all members except the while loops can be an exit member, and need to be handled with `checkAndExitStepBlock`. // waits and while loops are handled specially. - pbMembers.flatMap { + initialStepPatches ++ pbMembers.flatMap { // transform a wait statement into a step block (assuming the wait is a single cycle wait, due to previous stages) case wait: Wait => - val stepName = getStepName() + val stepName = getStepName(wait) nextStepBlock() val dsn = new MetaDesign( wait, @@ -103,35 +245,94 @@ case object DropRTWaits extends Stage: dsn.patch :: checkAndExitStepBlock(wait) // transform a while loop into a step block. case wb: DFLoop.DFWhileBlock if !wb.isCombinational => - val stepName = getStepName() - // the last member of the while loop is the exit member. - val lastLoopMember = wb.getVeryLastMember.get - // creating the if part of the while loop step block. - val stepAndIfDsn = new MetaDesign( - wb, - Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.FullReplacement) - ): - import dfhdl.core.{StepBlock, DFIf, DFBool, DFUnit} - val step = StepBlock.forced(using dfc.setName(stepName)) - dfc.enterOwner(step) - val cond = wb.guardRef.get.asValOf[DFBool] - val ifBlock = DFIf.Block(Some(cond), DFIf.Header(DFUnit)) - dfc.exitOwner() - // creating the else part of the while loop step block, to be applied when the while loop exits. - val elseDsn = new MetaDesign( - lastLoopMember, - Patch.Add.Config.After - ): - import dfhdl.core.DFIf - dfc.enterOwner(stepAndIfDsn.ifBlock) - ThisStep - dfc.exitOwner() - val elseBlock = DFIf.Block(None, stepAndIfDsn.ifBlock) - dfc.enterOwner(elseBlock) - NextStep - dfc.exitOwner() - enterStepBlock(elseDsn.patch) - Some(stepAndIfDsn.patch) + val stepName = getStepName(wb) + wb.getVeryLastMember match + // Empty loop body: generate the complete step+if/else structure in one shot. + case None => + nextStepBlock() + val dsn = new MetaDesign( + wb, + Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.FullReplacement) + ): + import dfhdl.core.{StepBlock, DFIf, DFBool, DFUnit} + val step = StepBlock.forced(using dfc.setName(stepName)) + dfc.enterOwner(step) + val wbGuard = wb.guardRef.get + val cond = wbGuard.asValOf[DFBool] + val ifBlock = DFIf.Block(Some(cond), DFIf.Header(DFUnit)) + // Stay inside `step` while building the if/else structure so that both + // the if-true-branch and the else-branch are owned by `step`, not by the + // enclosing ProcessBlock. (Previously `dfc.exitOwner()` was called here, + // which caused elseBlock and its Goto(NextStep) to be created at process + // level, breaking getOwnerStepBlock in FlattenStepBlocks.) + dfc.enterOwner(ifBlock) + ThisStep + dfc.exitOwner() + val elseBlock = DFIf.Block(None, ifBlock) + dfc.enterOwner(elseBlock) + NextStep + dfc.exitOwner() + dfc.exitOwner() + Some(dsn.patch) + // Non-empty loop body: the last member of the while loop is the exit member. + case Some(lastLoopMember) => + // creating the if part of the while loop step block. + val stepAndIfDsn = new MetaDesign( + wb, + Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.FullReplacement) + ): + import dfhdl.core.{StepBlock, DFIf, DFBool, DFUnit} + val step = StepBlock.forced(using dfc.setName(stepName)) + dfc.enterOwner(step) + val wbGuard = wb.guardRef.get + if (wb.isFallThrough) + val fallThrough = StepBlock.forced(using dfc.setName("fallThrough")) + dfc.enterOwner(fallThrough) + val clonedCond = !wbGuard.cloneAnonValueAndDepsHere.asValOf[DFBool] + dfhdl.core.DFVal.Alias.AsIs.ident(clonedCond)(using dfc.anonymize) + dfc.exitOwner() + end if + val cond = wbGuard.asValOf[DFBool] + val ifBlock = DFIf.Block(Some(cond), DFIf.Header(DFUnit)) + dfc.exitOwner() + // creating the else part of the while loop step block, to be applied when the while loop exits. + val elseDsn = new MetaDesign( + lastLoopMember, + Patch.Add.Config.After + ): + import dfhdl.core.DFIf + dfc.enterOwner(stepAndIfDsn.ifBlock) + ThisStep + dfc.exitOwner() + // Enter `step` explicitly so that elseBlock is owned by `step` (sibling of + // ifBlock), not by `ifBlock`. Without this, FullReplacement(wb → ifBlock) + // would redirect elseBlock's ownerRef to ifBlock, nesting it inside the + // if-true branch instead of at the same level. + dfc.enterOwner(stepAndIfDsn.step) + val elseBlock = DFIf.Block(None, stepAndIfDsn.ifBlock) + dfc.enterOwner(elseBlock) + NextStep + dfc.exitOwner() + dfc.exitOwner() + enterStepBlock(wb, lastLoopMember, Some(elseDsn.patch._2)) + Some(stepAndIfDsn.patch) + end match + // onEntry/onExit/fallThrough blocks must NOT be renamed: DropRTProcess identifies them + // by exact names "onEntry"/"onExit"/"fallThrough" via isOnEntry/isOnExit/isFallThrough. + // They also must NOT affect the step-name counter of their enclosing scope. + case stepBlock: StepBlock if !stepBlock.isRegular => None + case stepBlock: StepBlock => + val stepName = getStepName(stepBlock) + val lastStepBlockMember = stepBlock.getVeryLastMember.get + enterStepBlock(stepBlock, lastStepBlockMember, None) + if (stepBlock.getName != stepName) + Some( + stepBlock -> Patch.Replace( + stepBlock.setName(stepName), + Patch.Replace.Config.FullReplacement + ) + ) + else None case member => checkAndExitStepBlock(member) } }.flatten.toList diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropUnreferenced.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropUnreferenced.scala index 74171eaad..b8b42388a 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropUnreferenced.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/DropUnreferenced.scala @@ -25,7 +25,8 @@ case object DropUnreferencedAnons extends Stage, NoCheckStage: case Ident(_) => None case m: DFVal if m.isAnonymous && m.originMembers.isEmpty => Some(m -> Patch.Remove()) - case _ => None + case m: DFRange if m.originMembers.isEmpty => Some(m -> Patch.Remove()) + case _ => None } if (patchList.isEmpty) designDB else diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala index 1896f5dbf..b30f2f816 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ExplicitNamedVars.scala @@ -6,6 +6,28 @@ import dfhdl.compiler.patching.* import dfhdl.internals.* import dfhdl.options.CompilerOptions +/** This stage turns all named values to variables that get assigned. As a result, conditional + * expressions (if/match) are converted to statements. + * + * Additionally, this stage creates explicit register and variable declarations for named value + * expressions under RT process blocks. The named expression becomes a register when the + * declaration and usage are in different steps. Otherwise, it is a variable declaration. If the + * named expression is accessed both in the same step and in another step, then both variable and + * register declarations are created. Examples: + * {{{ + * val x = UInt(16) <> IN + * val y = UInt(16) <> OUT.REG init 0 + * process: + * def S0: Step = + * val v = x // becomes a variable declaration + * y.din := v + * NextStep + * val vr = x // becomes a register declaration + * def S1: Step = + * y.din := vr + 1 + * NextStep + * }}} + */ case object ExplicitNamedVars extends Stage: def dependencies: List[Stage] = List(NamedAnonCondExpr) def nullifies: Set[Stage] = Set(DropLocalDcls) @@ -22,7 +44,6 @@ case object ExplicitNamedVars extends Stage: case _ => false case _ => false } - final val WhenNotHeader = !WhenHeader extension (ch: DFConditional.Header) // recursive call to patch conditional block chains @@ -43,6 +64,24 @@ case object ExplicitNamedVars extends Stage: } end extension + extension (member: DFMember) + def getProcessOrStepBlockOwner(using MemberGetSet): Option[ProcessBlock | StepBlock] = + @scala.annotation.tailrec + def recur(current: DFMember): Option[ProcessBlock | StepBlock] = current match + case pb: ProcessBlock => Some(pb) + case sb: StepBlock => Some(sb) + case _: DFDesignBlock => None + case m => recur(m.getOwner) + recur(member.getOwner) + extension (dfVal: DFVal) + def getRegOrVarDeps(using MemberGetSet): (regs: Set[DFValReadDep], vars: Set[DFValReadDep]) = + val ownerOpt = dfVal.getProcessOrStepBlockOwner + ownerOpt match + case Some(owner) => + dfVal.getReadDeps.partition { _.getProcessOrStepBlockOwner.get != owner } + case None => (regs = Set(), vars = dfVal.getReadDeps) + end extension + def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = given RefGen = RefGen.fromGetSet val patchList = @@ -54,48 +93,100 @@ case object ExplicitNamedVars extends Stage: case _: DFVal.Dcl => None // ignoring constant declarations (named constants or derived constants) case DclConst() => None - // named if / match expressions will be changed to statements - case ch: DFConditional.Header => - // removing name and type from header - val updatedCH = ch match + // all other named values + case named => + // anonymized value + val anonValueIR = named match + // named ident requires removal of the ident itself + case Ident(value) => value + // removing name and type from header case mh: DFConditional.DFMatchHeader => mh.copy(dfType = DFUnit).anonymize - case ih: DFConditional.DFIfHeader => ih.copy(dfType = DFUnit).anonymize - // this variable will replace the header as a value + // removing name and type from header + case ih: DFConditional.DFIfHeader => ih.copy(dfType = DFUnit).anonymize + case _ => named.anonymize + val readDeps = named.getRegOrVarDeps + // println(s"regs: ${readDeps.regs}") + // println(s"vars: ${readDeps.vars}") + val regUse = readDeps.regs.nonEmpty + // var is also used if there are no dependencies at all + val varUse = readDeps.vars.nonEmpty || readDeps.regs.isEmpty && readDeps.vars.isEmpty val dsn = new MetaDesign( - ch, - Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.FullReplacement, WhenHeader) + named, + Patch.Add.Config.Before, + dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) ): - final val plantedNewVar = ch.asValAny.genNewVar(using dfc.setName(ch.getName)) - val newVarIR = plantedNewVar.asIR - plantMember(updatedCH) - val chPatchList = List( - // replacing all the references of header as a conditional header - dsn.patch, - // replacing all the references of header as a value - ch -> Patch.Replace( - dsn.newVarIR, - Patch.Replace.Config.ChangeRefOnly, - WhenNotHeader - ) - ) - chPatchList ++ ch.patchChains(dsn.newVarIR) - // all other named values - case named => - val anonIR = named.anonymize - val dsn = new MetaDesign(named, Patch.Add.Config.ReplaceWithFirst()): - final val plantedNewVar = named.asValAny.genNewVar(using dfc.setMeta(named.meta)) - plantedNewVar.:=(plantMember(anonIR).asValAny)(using - dfc.setMetaAnon(named.meta.position) - ) - List(dsn.patch) + val regDFC = dfc.setMeta(named.meta) + lazy val varDFC = if (regUse) regDFC.setName(s"${named.getName}_din") else regDFC + lazy val plantedNewVar = named.asValAny.dfType.<>(VAR)(using varDFC) + if (varUse) plantedNewVar // touch to trigger the lazy val + lazy val plantedNewReg = named.asValAny.dfType.<>(VAR.REG)(using regDFC) + if (regUse) plantedNewReg // touch to trigger the lazy val + val anonValue = named match + case Ident(_) => anonValueIR.asValAny + case _ => plantMember(anonValueIR).asValAny + named match + case _: DFConditional.Header => // do nothing + case _ => + if (varUse && regUse) + plantedNewVar := anonValue + plantedNewReg.din := plantedNewVar + else if (varUse) + if (named.isInEDDomain && !named.isInProcess) + plantedNewVar <> anonValue + else + plantedNewVar := anonValue + else if (regUse) + plantedNewReg.din := anonValue + val varPatchOpt = + if (varUse) + Some(named -> + Patch.Replace( + dsn.plantedNewVar.asIR, + Patch.Replace.Config.ChangeRefOnly, + Patch.Replace.RefFilter.OfMembers(readDeps.vars.asInstanceOf[Set[DFMember]]) + )) + else None + val regPatchOpt = + if (regUse) + Some(named -> + Patch.Replace( + dsn.plantedNewReg.asIR, + Patch.Replace.Config.ChangeRefOnly, + Patch.Replace.RefFilter.OfMembers(readDeps.regs.asInstanceOf[Set[DFMember]]) + )) + else None + // final named value handling according to the specialized use-cases + val finalizePatches = named match + // only ident values are removed completely, along with their references + case Ident(_) => Some(named -> Patch.Remove()) + case ch: DFConditional.Header => + val headerVar = if (varUse) dsn.plantedNewVar.asIR else dsn.plantedNewReg.asIR + // replacing all the references of header as a conditional header + val headerPatch = ch -> Patch.Replace( + anonValueIR, + Patch.Replace.Config.ChangeRefOnly, + WhenHeader + ) + // named header removal while preserving the references + ch -> Patch.Remove(true) :: + // setting the conditional blocks to reference the new anonymous header + headerPatch :: + // patching chains + ch.patchChains(headerVar) + // remove the member, but preserve the references + case _ => Some(named -> Patch.Remove(isMoved = true)) + Iterable( + Some(dsn.patch), + varPatchOpt, + regPatchOpt, + finalizePatches + ).flatten } .toList designDB.patch(patchList) end transform end ExplicitNamedVars -//This stage turns all named values to variables that get assigned. -//As a result, conditional expressions (if/match) are converted to statements. extension [T: HasDB](t: T) def explicitNamedVars(using CompilerOptions): DB = StageRunner.run(ExplicitNamedVars)(t.db) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala new file mode 100644 index 000000000..0ac86454a --- /dev/null +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/FlattenStepBlocks.scala @@ -0,0 +1,420 @@ +package dfhdl.compiler.stages + +import dfhdl.compiler.analysis.* +import dfhdl.compiler.ir.* +import dfhdl.compiler.patching.* +import dfhdl.options.CompilerOptions +import dfhdl.internals.* +import scala.annotation.tailrec +//format: off +/** This stage flattens the nested StepBlock hierarchy produced by [[DropRTWaits]] so that every + * StepBlock in an RT process becomes a direct child of the enclosing ProcessBlock, and resolves + * every relative `Goto` reference (`NextStep`, `ThisStep`, `FirstStep`) to an explicit reference + * to the concrete target StepBlock. After this stage, every `Goto` in every RT process carries + * an explicit `StepBlock` reference — no relative form remains. + * + * == Background == + * + * After [[DropRTWaits]], waits and while-loops inside an RT process have been converted into + * StepBlocks. When the original source contained nested wait/while constructs (e.g. a while loop + * with waits inside it, or a user-defined step block that contains further step blocks), the + * resulting IR reflects that nesting: some StepBlocks own other StepBlocks as children. + * Each step's control-flow terminus is a `Goto` member whose `stepRef` points to one of: + * - A concrete `StepBlock` — an explicit jump to that state (already resolved). + * - `Goto.NextStep` — advance to the "next" state in the sequential order (relative). + * - `Goto.ThisStep` — loop back to the current state (relative; used by while-loop steps). + * - `Goto.FirstStep` — jump to the first state of the process (relative). + * + * The three relative forms must all be resolved to explicit `StepBlock` references before + * [[DropRTProcess]] can generate the FSM. `NextStep` additionally requires hierarchy context + * that is destroyed by flattening, so all three are resolved here. + * + * == Transformation Rules == + * + * 1. Relative `NextStep` in the last step of a process (in DFS pre-order) wraps around to the + * first step. In all other steps `NextStep` advances to the immediately following step: + * ```scala + * // input + * process: + * def S0: Step = + * y.din := 0 + * NextStep + * end S0 + * def S1: Step = + * y.din := 1 + * NextStep + * end S1 + * // output + * process: + * def S0: Step = + * y.din := 0 + * S1 + * end S0 + * def S1: Step = + * y.din := 1 + * S0 + * end S1 + * ``` + * 2. `ThisStep` resolves to the enclosing step; `FirstStep` resolves to the first step of the + * process (DFS pre-order head): + * ```scala + * // input — S_0 loops to itself; S_1 jumps back to S_0 + * process: + * def S_0: Step = + * if (i) ThisStep else NextStep + * end S_0 + * def S_1: Step = + * if (i) FirstStep else NextStep + * end S_1 + * def S_2: Step = NextStep + * end S_2 + * // output + * process: + * def S_0: Step = + * if (i) S_0 else S_1 + * end S_0 + * def S_1: Step = + * if (i) S_0 else S_2 + * end S_1 + * def S_2: Step = S_0 + * end S_2 + * ``` + * 3. Non-step statements that appear between consecutive steps at any nesting level are relocated + * into the body of the immediately preceding step (before its terminal `NextStep` goto). + * They are placed at the end of the deepest last-child step, so they execute just before + * control leaves that sub-tree: + * ```scala + * // input + * process: + * def S_0: Step = NextStep + * end S_0 + * x.din := i // inter-step statement + * def S_1: Step = NextStep + * end S_1 + * // output — x.din := i moved into S_0 before its goto + * process: + * def S_0: Step = + * x.din := i + * S_1 + * end S_0 + * def S_1: Step = S_0 + * end S_1 + * ``` + * 4. Nested StepBlocks (a step that directly contains another step) are lifted one level at a + * time until all steps are direct children of the ProcessBlock. The parent step's `NextStep` + * is replaced by a goto to the first child step; the last child's `NextStep` becomes the + * former parent's `NextStep` target: + * ```scala + * // input + * process: + * def MyStep: Step = + * def MyStep_0: Step = NextStep + * end MyStep_0 + * NextStep + * end MyStep + * // output + * process: + * def MyStep: Step = MyStep_0 + * end MyStep + * def MyStep_0: Step = MyStep + * end MyStep_0 + * ``` + * 5. A StepBlock nested directly inside a conditional branch is extracted to ProcessBlock level. + * A goto to that step replaces it in the branch; the "consumed Goto" that immediately followed + * it in the branch (encoding what happens when the step sequence ends) is removed and its + * target becomes the extracted step's terminal goto: + * ```scala + * // input + * process: + * def S_0: Step = + * if (i) + * def S_0_0: Step = NextStep + * end S_0_0 + * ThisStep // consumed goto: S_0_0's next is S_0 + * else + * NextStep // else branch: S_0's next is S_1 + * end if + * end S_0 + * def S_1: Step = NextStep + * end S_1 + * // output + * process: + * def S_0: Step = + * if (i) S_0_0 + * else S_1 + * end S_0 + * def S_0_0: Step = S_0 // NextStep of S_0_0 resolved via consumed ThisStep -> S_0 + * end S_0_0 + * def S_1: Step = S_0 + * end S_1 + * ``` + * + * == Implementation Phases == + * + * The stage applies four sequential `db.patch()` calls to avoid patch conflicts: + * + * - **Phase 0** (inter-step relocation): moves trailing statements before the `NextStep` Goto of + * `deepestLastChild(stepI)` — processed inner-first so Move patches concatenate correctly. + * - **Phase 1** (conditional extraction): uses the Phase-0 DB so relocated statements travel with + * the extracted step. Inserts a goto at the branch site, removes the consumed Goto, moves step + * and all descendants to ProcessBlock level. + * - **Phase 2** (structural flattening): one level per `@tailrec` pass — each pass moves direct + * nested children (with full `Flattened` descendants) up one level. + * - **Phase 3** (goto resolution): `ChangeRef` patches computed from the *original* DB, so + * `nextStepMap` and `conditionalStepMap` remain correct regardless of structural changes made + * in Phases 0–2. + */ +//format: on +case object FlattenStepBlocks extends Stage: + def dependencies: List[Stage] = List(DropRTWaits, ExplicitNamedVars, DropLocalDcls) + def nullifies: Set[Stage] = Set() + + def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = + given RefGen = RefGen.fromGetSet + // Phase 3 ChangeRef patches are computed from the original DB. + val gotoPatchList = designDB.members.view.flatMap { + case pb: ProcessBlock if pb.isInRTDomain => collectGotoPatches(pb) + case _ => Nil + }.toList + // Phase 0: inter-step relocation (Step 5 inter-step + Step 6) + val db0 = designDB.patch( + designDB.members.view.flatMap { + case pb: ProcessBlock if pb.isInRTDomain => collectInterStepPatches(pb) + case _ => Nil + }.toList + ) + // Phase 1: conditional branch extraction (uses db0 for updated member structure) + val db1 = locally { + given MemberGetSet = db0.getSet + db0.patch( + db0.members.view.flatMap { + case pb: ProcessBlock if pb.isInRTDomain => collectConditionalExtractionPatches(pb) + case _ => Nil + }.toList + ) + } + // Phase 2: structural flattening, one level at a time (uses db1, applied repeatedly) + val db2 = flattenRepeatedly(db1) + // Phase 3: Goto ChangeRef + db2.patch(gotoPatchList) + end transform + + // Repeatedly flatten one nesting level of StepBlocks until all are direct pb children. + @tailrec private def flattenRepeatedly(db: DB)(using RefGen): DB = + given MemberGetSet = db.getSet + val patches = db.members.view.flatMap { + case pb: ProcessBlock if pb.isInRTDomain => collectFlattenPatchesOneLevel(pb) + case _ => Nil + }.toList + if patches.isEmpty then db + else flattenRepeatedly(db.patch(patches)) + + // --- Shared helpers --- + + private def collectDirectFlatSteps(owner: DFOwner)(using MemberGetSet): List[StepBlock] = + owner.members(MemberView.Folded).flatMap { + case sb: StepBlock if sb.isRegular => sb :: collectDirectFlatSteps(sb) + case _ => Nil + } + + private def findConsumedGoto(s: StepBlock)(using MemberGetSet): (DFConditional.Block, Goto) = + val cb = s.getOwner.asInstanceOf[DFConditional.Block] + val cbMembers = cb.members(MemberView.Folded) + val sIdx = cbMembers.indexOf(s) + val consumedGoto = cbMembers.drop(sIdx + 1).collectFirst { case g: Goto => g }.get + (cb, consumedGoto) + + // Returns the next regular StepBlock sibling inside the same conditional branch, if any. + private def findNextStepInBranch(s: StepBlock)(using MemberGetSet): Option[StepBlock] = + val cb = s.getOwner.asInstanceOf[DFConditional.Block] + val cbMembers = cb.members(MemberView.Folded) + val sIdx = cbMembers.indexOf(s) + cbMembers.drop(sIdx + 1).collectFirst { case sb: StepBlock if sb.isRegular => sb } + + private def deepestLastChild(step: StepBlock)(using MemberGetSet): StepBlock = + step.members(MemberView.Folded) + .collect { case sb: StepBlock if sb.isRegular => sb } + .lastOption match + case None => step + case Some(last) => deepestLastChild(last) + + private def findNextStepGoto(step: StepBlock)(using MemberGetSet): Option[Goto] = + // Restrict to gotos whose enclosing StepBlock is `step` itself, not a nested step. + // Without this guard, pre-order DFS would find a nested step's Goto(NextStep) first, + // causing inter-step statements to be incorrectly moved into an inner step's body. + step.members(MemberView.Flattened).collectFirst { + case g: Goto if g.stepRef.get == Goto.NextStep && g.getOwnerStepBlock == step => g + } + + // --- Phase 3: Goto ChangeRef patches (computed from original DB) --- + + private def collectGotoPatches(pb: ProcessBlock)(using MemberGetSet): List[(DFMember, Patch)] = + val flatSteps = collectDirectFlatSteps(pb) + if flatSteps.isEmpty then return Nil + val nextStepMap = (flatSteps lazyZip (flatSteps.tail :+ flatSteps.head)).toMap + val firstStep = flatSteps.head + val conditionalBranchSteps = pb.members(MemberView.Flattened).collect { + case sb: StepBlock if sb.isRegular && sb.getOwner.isInstanceOf[DFConditional.Block] => sb + } + val consumedGotos = conditionalBranchSteps.map(findConsumedGoto(_)._2).toSet + val conditionalStepMap = conditionalBranchSteps.map { s => + // Non-last steps in a branch target the next step in the branch directly. + // Only the last step uses the branch-terminal consumed goto to find its target. + val target: StepBlock = findNextStepInBranch(s).getOrElse { + val (_, consumedGoto) = findConsumedGoto(s) + consumedGoto.stepRef.get match + case sb: StepBlock => sb + case Goto.ThisStep => consumedGoto.getOwnerStepBlock + case Goto.NextStep => nextStepMap(consumedGoto.getOwnerStepBlock) + case Goto.FirstStep => firstStep + } + s -> target + }.toMap + pb.members(MemberView.Flattened) + .collect { case g: Goto if !consumedGotos.contains(g) => g } + .flatMap { g => + g.stepRef.get match + case _: StepBlock => None + case Goto.ThisStep => + Some(g -> Patch.ChangeRef(_.asInstanceOf[Goto].stepRef, g.getOwnerStepBlock)) + case Goto.FirstStep => + Some(g -> Patch.ChangeRef(_.asInstanceOf[Goto].stepRef, firstStep)) + case Goto.NextStep => + val owningStep = g.getOwnerStepBlock + val target = conditionalStepMap.getOrElse(owningStep, nextStepMap(owningStep)) + Some(g -> Patch.ChangeRef(_.asInstanceOf[Goto].stepRef, target)) + } + end collectGotoPatches + + // --- Phase 0: Inter-step relocation patches --- + + private def collectInterStepPatches( + pb: ProcessBlock + )(using MemberGetSet): List[(DFMember, Patch)] = + val flatSteps = collectDirectFlatSteps(pb) + if flatSteps.isEmpty then return Nil + // Step 5 inter-step: relocate statements in conditional branches into the step's body + val conditionalBranchSteps = pb.members(MemberView.Flattened).collect { + case sb: StepBlock if sb.isRegular && sb.getOwner.isInstanceOf[DFConditional.Block] => sb + } + val step5InterStep = conditionalBranchSteps.flatMap { s => + val (cb, consumedGoto) = findConsumedGoto(s) + val cbMembers = cb.members(MemberView.Folded) + val sIdx = cbMembers.indexOf(s) + val consumedGotoIdx = cbMembers.indexOf(consumedGoto) + // When multiple steps share the same branch, only collect statements up to the next + // step (not all the way to the consumed goto). Otherwise the same statements would be + // collected for every step that precedes them, producing duplicate Move patches. + val upperIdx = findNextStepInBranch(s) + .map(cbMembers.indexOf) + .getOrElse(consumedGotoIdx) + val interStepStmts = cbMembers.slice(sIdx + 1, upperIdx) + .filterNot(m => m.isInstanceOf[StepBlock] || m.isInstanceOf[Goto]) + val targetStep = deepestLastChild(s) + findNextStepGoto(targetStep).toList.flatMap { nextStepGoto => + interStepStmts.map { stmt => + nextStepGoto -> Patch.Move(List(stmt), stmt.getOwner, Patch.Move.Config.Before) + } + } + } + // Step 6: relocate inter-step statements at each nesting level (inner-first for correct order) + def collectOwners(owner: DFOwner): List[DFOwner] = + owner.members(MemberView.Folded) + .collect { case sb: StepBlock if sb.isRegular => sb } + .flatMap(collectOwners) :+ owner + val step6 = collectOwners(pb).flatMap { owner => + val directMembers = owner.members(MemberView.Folded) + val directSteps = directMembers.collect { case sb: StepBlock if sb.isRegular => sb } + if directSteps.isEmpty then Nil + else + directSteps.zipWithIndex.flatMap { (step, idx) => + val stepPos = directMembers.indexOf(step) + val nextPos = + if idx + 1 < directSteps.length then directMembers.indexOf(directSteps(idx + 1)) + else directMembers.length + val stmtsToMove = directMembers.slice(stepPos + 1, nextPos).filterNot { + case _: StepBlock => true + case _: Goto => true + case _ => false + } + if stmtsToMove.isEmpty then Nil + else + val targetStep = deepestLastChild(step) + findNextStepGoto(targetStep).toList.flatMap { nextStepGoto => + stmtsToMove.map { stmt => + nextStepGoto -> Patch.Move(List(stmt), stmt.getOwner, Patch.Move.Config.Before) + } + } + } + end if + } + step5InterStep ++ step6 + end collectInterStepPatches + + // --- Phase 1: Conditional branch extraction (uses db0's member structure) --- + + private def collectConditionalExtractionPatches( + pb: ProcessBlock + )(using MemberGetSet, RefGen): List[(DFMember, Patch)] = + val flatSteps = collectDirectFlatSteps(pb) + if flatSteps.isEmpty then return Nil + val conditionalBranchSteps = pb.members(MemberView.Flattened).collect { + case sb: StepBlock if sb.isRegular && sb.getOwner.isInstanceOf[DFConditional.Block] => sb + } + conditionalBranchSteps.flatMap { s => + val (cb, consumedGoto) = findConsumedGoto(s) + val cbMembers = cb.members(MemberView.Folded) + val sIdx = cbMembers.indexOf(s) + val isFirstStepInBranch = !cbMembers.take(sIdx).exists(_.isInstanceOf[StepBlock]) + val isLastStepInBranch = findNextStepInBranch(s).isEmpty + // Insert an explicit Goto to s at s's former position in the branch only for the first + // step. Subsequent steps in the same branch are reached via the preceding step's goto. + val dsnPatchOpt: Option[(DFMember, Patch)] = + if isFirstStepInBranch then + Some( + new MetaDesign(s, Patch.Add.Config.Before): + import dfhdl.core.* + Goto(s.refTW[Goto], dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags).addMember + .patch + ) + else None + // Remove the consumed goto only for the last step so it is not removed twice when + // multiple steps share the same branch-terminal goto. + val removeConsumedGotoOpt: Option[(DFMember, Patch)] = + if isLastStepInBranch then Some(consumedGoto -> Patch.Remove()) else None + // Move s and ALL its descendants to after the parent step at pb level. + // Including descendants ensures the flat member list maintains valid ownership ordering. + val parentStep = cb.getOwnerStepBlock + val allMembersToMove = s :: s.members(MemberView.Flattened) + val movePatch: (DFMember, Patch) = + parentStep -> Patch.Move(allMembersToMove, cb, Patch.Move.Config.After) + dsnPatchOpt.toList ++ List(movePatch) ++ removeConsumedGotoOpt + } + end collectConditionalExtractionPatches + + // --- Phase 2: One-level structural flattening --- + + private def collectFlattenPatchesOneLevel( + pb: ProcessBlock + )(using MemberGetSet): List[(DFMember, Patch)] = + // For each direct pb-child step, lift its immediate nested StepBlock children one level up. + // Each lift moves the child and ALL its descendants so the flat member list stays valid. + // Multiple levels require repeated application (see flattenRepeatedly). + pb.members(MemberView.Folded).flatMap { + case topAncestor: StepBlock if topAncestor.isRegular => + topAncestor.members(MemberView.Folded).flatMap { + case child: StepBlock if child.isRegular => + val allMembersToMove = child :: child.members(MemberView.Flattened) + List( + topAncestor -> Patch.Move(allMembersToMove, child.getOwner, Patch.Move.Config.After) + ) + case _ => Nil + } + case _ => Nil + } + end collectFlattenPatchesOneLevel +end FlattenStepBlocks + +extension [T: HasDB](t: T) + def flattenStepBlocks(using CompilerOptions): DB = + StageRunner.run(FlattenStepBlocks)(t.db) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala index d12adc6e5..efd3faa98 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/GlobalizePortVectorParams.scala @@ -8,7 +8,7 @@ import dfhdl.options.CompilerOptions import scala.collection.immutable.ListMap import scala.collection.mutable import dfhdl.compiler.stages.vhdl.VHDLDialect -import dfhdl.core.{DFTypeAny, asFE} +import dfhdl.core.{DFTypeAny, asFE, refTW} import dfhdl.compiler.ir.DFVal.PortByNameSelect /** This stage globalizes design parameters that set port vector lengths. This is needed only for @@ -26,181 +26,222 @@ case object GlobalizePortVectorParams extends Stage: case _ => false def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = given RefGen = RefGen.fromGetSet - // to collect unique design parameters while maintaining order for consistent compilation and dependency - val designParams = mutable.LinkedHashSet.empty[DFVal] - // check ref - def checkRef(ref: DFRef.TwoWayAny): Int = - ref.get match - case dfVal: DFVal => - if (dfVal.isGlobal) 0 - else - val ret = dfVal.getRefs.map(checkRef).sum - if (dfVal.isAnonymous) ret - else - designParams += dfVal - ret + 1 - case _ => 0 - // check int param ref - def checkIntParamRef(intParamRef: IntParamRef): Int = - intParamRef.getRef.map(checkRef).getOrElse(0) - - // checking vector types - def checkVector(dfType: DFType): Int = dfType match - case dt: DFVector => - // checking vector dimensions for parameters we need to globalize - val dimParamCnt = dt.cellDimParamRefs.map(checkIntParamRef).sum - // checking vector cell type for composed dependency on parameters we need to globalize - val cellTypeParamCnt = dt.cellType match - case DFBits(widthParamRef) => checkIntParamRef(widthParamRef) - case DFUInt(widthParamRef) => checkIntParamRef(widthParamRef) - case DFSInt(widthParamRef) => checkIntParamRef(widthParamRef) - case dt: DFVector => checkVector(dt.cellType) - case _ => 0 - dimParamCnt + cellTypeParamCnt - case _ => 0 - val vecTypeReplaceMap = mutable.Map.empty[DFVector, DFVector] - // vector type extractor through reference and port by name select - object VectorNetNodeType: - def unapply(ref: DFRefAny): Option[DFVector] = ref.get match - case PortByNameSelect.Of(DFVector.Val(dfType)) => Some(dfType) - case DFVector.Val(dfType) => Some(dfType) - case _ => None - designDB.members.foreach { - // checking all ports - case dcl @ DclPort() => checkVector(dcl.dfType) - // checking all port by name selects that change their type - case pbns @ PortByNameSelect.Of(DFVector.Val(dclType)) if pbns.dfType != dclType => - vecTypeReplaceMap += pbns.dfType.asInstanceOf[DFVector] -> dclType - // checking all assignments/connections between vectors that are considered to be similar types, - // but are not exactly the same (e.g., two vectors types referencing a `(LEN + 1)` length value) - case net @ DFNet(lhsRef = VectorNetNodeType(lhsType), rhsRef = VectorNetNodeType(rhsType)) - if !(lhsType == rhsType) && lhsType.isSimilarTo(rhsType) => - val lhsCnt = checkVector(lhsType) - val rhsCnt = checkVector(rhsType) - if (lhsCnt < rhsCnt) vecTypeReplaceMap += lhsType -> rhsType - else if (lhsCnt > rhsCnt) vecTypeReplaceMap += rhsType -> lhsType - case _ => - } - val vecTypeReplacePatches = designDB.members.collect { - case dfVal @ DFVector.Val(dfType) if vecTypeReplaceMap.contains(dfType) => - dfVal -> Patch.Replace( - dfVal.updateDFType(vecTypeReplaceMap(dfType)), - Patch.Replace.Config.FullReplacement - ) - } - def movedMembers(namedParam: DFVal): List[DFVal] = - namedParam.collectRelMembers(true).filterNot(_.isGlobal) - - val addedGlobals = designParams.view.flatMap(movedMembers).toList - val dupDesignSet = designParams.view.map(_.getOwnerDesign).toSet - // origin -> duplicates design map - val dupDesignMap = dupDesignSet.groupBy(_.dclName).values.map { grp => - val (dups, orig) = grp.partition(_.isDuplicate) - assert(orig.size == 1) - orig.head -> dups.toList - }.toMap - val dupRefTable = mutable.Map.empty[DFRefAny, DFMember] - val dupDesignMembersMap = mutable.Map.empty[DFDesignBlock, List[DFMember]] - // go through all designs that require member duplication from their original design - dupDesignMap.foreach { (orig, dups) => - val origMembers = designDB.designMemberTable(orig) - dups.foreach { dup => - val dupPublicMembers = designDB.designMemberTable(dup) - // orig->dup replacement map memoization for reference mapping in `dupRefTable` + // First, reconstruct members for duplicate designs (which have no members in the DB). + // This is needed so the parameter analysis below can find design params for all designs. + val dupDesignDB = + val dupRefTable = mutable.Map.empty[DFRefAny, DFMember] + val dupDesignMembersMap = mutable.Map.empty[DFDesignBlock, List[DFMember]] + // Duplicate all members of a design, recursively handling nested designs. + def duplicateDesignMembers( + orig: DFDesignBlock, + dup: DFDesignBlock + ): Unit = + val origMembers = designDB.designMemberTable(orig) val origToDupMemberMap = mutable.Map.empty[DFMember, DFMember] - // add the designs to the replacement map origToDupMemberMap += orig -> dup - // collect all public members (they are not necessarily at the top of the design) - val origPublicMembers = origMembers.filterPublicMembers - val origPublicMembersSet = origPublicMembers.toSet - assert(origPublicMembers.length == dupPublicMembers.length) - // adding public members to the orig->dup member replacement map - origPublicMembers.lazyZip(dupPublicMembers).foreach(origToDupMemberMap += _) - // if the replacement map has a member, get its mapped replacement, otherwise the member - // remains as is for reference change def getReplacement(member: DFMember): DFMember = origToDupMemberMap.getOrElse(member, member) val dupMembers = origMembers.map { origMember => - // a public member already has an equivalent as a dup design member - if (origPublicMembersSet.contains(origMember)) origToDupMemberMap(origMember) - // a non-public member needs to be duplicated with new references to be mapped accordingly - else - // duplicate member - val dupMember = origMember.copyWithNewRefs - // add to replacement map - origToDupMemberMap += origMember -> dupMember - // add duplicated member owner reference - dupRefTable += dupMember.ownerRef -> getReplacement(origMember.getOwner) - // add duplicated member relative references - origMember.getRefs.lazyZip(dupMember.getRefs).foreach { (origRef, dupRef) => - dupRefTable += dupRef -> getReplacement(origRef.get) - } - dupMember + val dupMember0 = origMember.copyWithNewRefs + // Tag nested design blocks as duplicates + val dupMember = dupMember0 match + case dsn: DFDesignBlock => dsn.setTags(_.tag(DuplicateTag)).asInstanceOf[dupMember0.type] + case _ => dupMember0 + origToDupMemberMap += origMember -> dupMember + dupRefTable += dupMember.ownerRef -> getReplacement(origMember.getOwner) + origMember.getRefs.lazyZip(dupMember.getRefs).foreach { (origRef, dupRef) => + dupRefTable += dupRef -> getReplacement(origRef.get) + } + dupMember } dupDesignMembersMap += dup -> dupMembers + // Recursively duplicate nested designs that were also removed + origMembers.lazyZip(dupMembers).foreach { + case (origNested: DFDesignBlock, dupNested: DFDesignBlock) + if !designDB.designMemberTable.contains(dupNested) => + duplicateDesignMembers(origNested, dupNested) + case _ => + } + designDB.dupDesignToOrigMap.groupBy(_._2).foreach { (orig, dupMap) => + dupMap.keys.foreach { dup => duplicateDesignMembers(orig, dup) } } - } + def populateWithDupMembers(members: List[DFMember]): List[DFMember] = + members.flatMap { + case design: DFDesignBlock => + design :: populateWithDupMembers( + dupDesignMembersMap.getOrElse(design, designDB.designMemberTable(design)) + ) + case member => Some(member) + } + designDB.copy( + members = designDB.membersGlobals ++ populateWithDupMembers(List(designDB.top)), + refTable = designDB.refTable ++ dupRefTable + ) + end dupDesignDB + // Now run the analysis on the reconstructed DB with all design members present. + locally { + given MemberGetSet = dupDesignDB.getSet + // to collect unique design parameters while maintaining order for consistent compilation and dependency + val designParams = mutable.LinkedHashSet.empty[DFVal] + // check ref + def checkRef(ref: DFRef.TwoWayAny): Int = + ref.get match + case dfVal: DFVal => + if (dfVal.isGlobal) 0 + else + val ret = dfVal.getRefs.map(checkRef).sum + if (dfVal.isAnonymous) ret + else + designParams += dfVal + ret + 1 + case _ => 0 + // check int param ref + def checkIntParamRef(intParamRef: IntParamRef): Int = + intParamRef.getRef.map(checkRef).getOrElse(0) - // manually the duplicated members and then using patch for replacing - // the design parameters with global parameters - def populateWithDupMembers(members: List[DFMember]): List[DFMember] = - members.flatMap { - case design: DFDesignBlock => - design :: populateWithDupMembers( - dupDesignMembersMap.getOrElse(design, designDB.designMemberTable(design)) - ) - case member => Some(member) + // checking vector types + def checkVector(dfType: DFType): Int = dfType match + case dt: DFVector => + // checking vector dimensions for parameters we need to globalize + val dimParamCnt = dt.cellDimParamRefs.map(checkIntParamRef).sum + // checking vector cell type for composed dependency on parameters we need to globalize + val cellTypeParamCnt = dt.cellType match + case DFBits(widthParamRef) => checkIntParamRef(widthParamRef) + case DFUInt(widthParamRef) => checkIntParamRef(widthParamRef) + case DFSInt(widthParamRef) => checkIntParamRef(widthParamRef) + case dt: DFVector => checkVector(dt.cellType) + case _ => 0 + dimParamCnt + cellTypeParamCnt + case _ => 0 + val vecTypeReplaceMap = mutable.Map.empty[DFVector, DFVector] + // vector type extractor through reference and port by name select + object VectorNetNodeType: + def unapply(ref: DFRefAny): Option[DFVector] = ref.get match + case PortByNameSelect.Of(DFVector.Val(dfType)) => Some(dfType) + case DFVector.Val(dfType) => Some(dfType) + case _ => None + dupDesignDB.members.foreach { + // checking all ports + case dcl @ DclPort() => checkVector(dcl.dfType) + // checking all port by name selects that change their type + case pbns @ PortByNameSelect.Of(DFVector.Val(dclType)) if pbns.dfType != dclType => + vecTypeReplaceMap += pbns.dfType.asInstanceOf[DFVector] -> dclType + // checking all assignments/connections between vectors that are considered to be similar types, + // but are not exactly the same (e.g., two vectors types referencing a `(LEN + 1)` length value) + case net @ DFNet(lhsRef = VectorNetNodeType(lhsType), rhsRef = VectorNetNodeType(rhsType)) + if !(lhsType == rhsType) && lhsType.isSimilarTo(rhsType) => + val lhsCnt = checkVector(lhsType) + val rhsCnt = checkVector(rhsType) + // when a PBNS connects to a non-PBNS, always unify to the child design's port type + // so that after globalization both sides share the same instance-specific globals + val lhsIsPbns = net.lhsRef.get.isInstanceOf[DFVal.PortByNameSelect] + val rhsIsPbns = net.rhsRef.get.isInstanceOf[DFVal.PortByNameSelect] + if (lhsIsPbns && !rhsIsPbns) vecTypeReplaceMap += rhsType -> lhsType + else if (rhsIsPbns && !lhsIsPbns) vecTypeReplaceMap += lhsType -> rhsType + else if (lhsCnt < rhsCnt) vecTypeReplaceMap += lhsType -> rhsType + else if (lhsCnt > rhsCnt) vecTypeReplaceMap += rhsType -> lhsType + case _ => } - val dupDesignDB = designDB.copy( - members = designDB.membersGlobals ++ populateWithDupMembers(List(designDB.top)), - refTable = designDB.refTable ++ dupRefTable - ) + // Split vecTypeReplace into PBNS patches and non-PBNS patches. + // PBNS replacements introduce TypeRefs from port declarations (shared TypeRefs). + // If applied in the same patch batch as port replacements (which purge those same TypeRefs), + // the TypeRefs get removed before the PBNS can reference them. + // Apply PBNS replacements first so the shared TypeRefs have a higher repeat count. + val (vecTypePbnsPatches, vecTypeOtherPatches) = dupDesignDB.members.collect { + case dfVal @ DFVector.Val(dfType) if vecTypeReplaceMap.contains(dfType) => + dfVal -> Patch.Replace( + dfVal.updateDFType(vecTypeReplaceMap(dfType)), + Patch.Replace.Config.FullReplacement + ) + }.partition((m, _) => m.isInstanceOf[DFVal.PortByNameSelect]) + def movedMembers(namedParam: DFVal): List[DFVal] = + namedParam match + // for DesignParams, also collect anonymous deps of the actual param value + // (from paramMap), since collectRelMembers only follows getRefs which + // does not include the paramMap value reference + case param: DFVal.DesignParam => + param.appliedOrDefaultVal.collectRelMembers(false).filterNot(_.isGlobal) ++ List(param) + case _ => + namedParam.collectRelMembers(true).filterNot(_.isGlobal) - // patches to change the duplicated design declaration names to a unique identifier according - // to theie instance names - val designPatches = dupDesignMap.view.flatMap { - case (orig, dups) if dups.length >= 1 => - (orig :: dups).map { design => - val updatedDclMeta = - design.dclMeta.setName( - s"${design.dclName}_${design.getFullName.replaceAll("\\.", "_")}" + val addedGlobals = designParams.view.flatMap(movedMembers).toList.distinct + val dupDesignSet = designParams.view.map(_.getOwnerDesign).toSet + // origin -> duplicates design map + val dupDesignMap = dupDesignSet.groupBy(_.dclName).values.map { grp => + val (dups, orig) = grp.partition(_.isDuplicate) + assert(orig.size == 1) + orig.head -> dups.toList + }.toMap + + // patches to change the duplicated design declaration names to a unique identifier according + // to their instance names. + // Only remove globalized params from paramMap; keep unglobalized ones intact. + val globalizedParamNamesPerDesign: Map[DFDesignBlock, Set[String]] = + designParams.view.collect { case dp: DFVal.DesignParam => dp } + .groupBy(_.getOwnerDesign) + .view.mapValues(_.map(_.getName).toSet).toMap + def prunedParamMap(design: DFDesignBlock): ListMap[String, DFDesignBlock.ParamRef] = + val toRemove = globalizedParamNamesPerDesign.getOrElse(design, Set.empty) + if (toRemove.isEmpty) design.paramMap + else design.paramMap.filterNot((name, _) => toRemove.contains(name)) + val designPatches = dupDesignMap.view.flatMap { + case (orig, dups) if dups.length >= 1 => + (orig :: dups).map { design => + val updatedDclMeta = + design.dclMeta.setName( + s"${design.dclName}_${design.getFullName.replaceAll("\\.", "_")}" + ) + val updatedDesign = design.removeTagOf[DuplicateTag].copy( + dclMeta = updatedDclMeta, + paramMap = prunedParamMap(design) ) - val updatedDesign = design.removeTagOf[DuplicateTag].copy(dclMeta = updatedDclMeta) - design -> Patch.Replace(updatedDesign, Patch.Replace.Config.FullReplacement) - } - case _ => None - }.toList - // add global parameters before the design top - val dsn = new MetaDesign(dupDesignDB.top, Patch.Add.Config.Before): - // patches to replace with properly named parameter or just move the anonymous members - val replacePatches = addedGlobals.map { - // design parameters are transformed into global as-is named aliases - case param: DFVal.DesignParam => - val updatedMeta = param.meta.setName(param.getFullName.replaceAll("\\.", "_")) - val globalParam = - DFVal.Alias.AsIs( + design -> Patch.Replace(updatedDesign, Patch.Replace.Config.FullReplacement) + } + case (orig, _) => + Some( + orig -> + Patch.Replace( + orig.copy(paramMap = prunedParamMap(orig)), + Patch.Replace.Config.FullReplacement + ) + ) + }.toList + val paramReplacementMap = mutable.Map.empty[DFVal, DFVal] + def getUpdatedParamValue(param: DFVal.DesignParam): DFVal = + var dfVal = param.appliedOrDefaultVal + while (paramReplacementMap.contains(dfVal)) dfVal = paramReplacementMap(dfVal) + dfVal + // add global parameters before the design top + val dsn = new MetaDesign(dupDesignDB.top, Patch.Add.Config.Before): + // patches to replace with properly named parameter or just move the anonymous members + val replacePatches = addedGlobals.map { + // design parameters are transformed into global as-is named aliases + case param: DFVal.DesignParam => + val updatedMeta = param.meta.setName(param.getFullName.replaceAll("\\.", "_")) + val updatedParamVal = getUpdatedParamValue(param) + val globalParam = dfhdl.core.DFVal.Alias.AsIs.forced( param.dfType, - // TODO: the class tag here is incorrect (currently is DesignParam) and should be fixed or... - // do we really need the tags at all? - param.dfValRef.asInstanceOf[DFVal.Alias.PartialRef], - param.ownerRef, - updatedMeta, - param.tags - ) - plantMember(globalParam) - param -> Patch.Replace(globalParam, Patch.Replace.Config.ChangeRefAndRemove) - case m if !m.isAnonymous => - val globalParam = m.setName(m.getFullName.replaceAll("\\.", "_")) - plantMember(globalParam) - m -> Patch.Replace(globalParam, Patch.Replace.Config.ChangeRefAndRemove) - case m => - plantMember(m) - m -> Patch.Remove(isMoved = true) - } - // TODO: when combined to a single patch, there is a bug that prevents some members to get a global ownership - dupDesignDB - .patch(dsn.patch :: dsn.replacePatches ++ vecTypeReplacePatches) - .patch(designPatches) + updatedParamVal + )(using dfc.setMeta(updatedMeta)) + paramReplacementMap += param -> globalParam + param -> Patch.Replace(globalParam, Patch.Replace.Config.ChangeRefAndRemove) + case m: DFVal if !m.isAnonymous => + val globalParam = m.setName(m.getFullName.replaceAll("\\.", "_")) + plantMember(globalParam) + paramReplacementMap += m -> globalParam + m -> Patch.Replace(globalParam, Patch.Replace.Config.ChangeRefAndRemove) + case m => + plantMember(m) + m -> Patch.Remove(isMoved = true) + } + // TODO: when combined to a single patch, there is a bug that prevents some members to get a global ownership + // Apply PBNS type patches first (they introduce shared TypeRefs), then other type patches + // (which may purge those same TypeRefs from the originals). + dupDesignDB + .patch(dsn.patch :: dsn.replacePatches ++ vecTypePbnsPatches) + .patch(vecTypeOtherPatches) + .patch(designPatches) + } end transform end GlobalizePortVectorParams diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/LocalToDesignParams.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/LocalToDesignParams.scala index 099c5d6b6..a78d3555e 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/LocalToDesignParams.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/LocalToDesignParams.scala @@ -6,8 +6,9 @@ import dfhdl.compiler.patching.* import dfhdl.options.CompilerOptions import scala.collection.mutable -/** This stage converts local parameters that are used in IOs to be design parameters, since VHDL - * does not support local parameters for IO access. +/** This stage converts local parameters that are used in IOs to be design parameters with default + * values, since VHDL does not support local parameters for IO access. These kind of design + * parameters remain at their default (relative) values and are never directly applied. */ case object LocalToDesignParams extends Stage: override def runCondition(using co: CompilerOptions): Boolean = diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/OrderMembers.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/OrderMembers.scala index b961f53b2..d69ade63b 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/OrderMembers.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/OrderMembers.scala @@ -38,8 +38,8 @@ object OrderMembers: // design parameters come second as they are dependent only on external // initialization or default values and everything else can depend on them case _: DFVal.DesignParam => 2 - // anonymous members that are referenced by declarations come third - case dfVal: DFVal if dfVal.isReferencedByAnyDcl => 3 + // anonymous members that are referenced by declarations or design instances come third + case dfVal: DFVal if dfVal.isReferencedByAnyDclOrDesign => 3 // fourth to come are constant declarations that may be referenced by ports case DclConst() => 4 // fifth are ports diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala index be16ae08b..a6994dfd5 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SanityCheck.scala @@ -41,11 +41,23 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: case m: DFDesignBlock if !m.isTop => if (!refTable.contains(m.ownerRef)) reportViolation(s"Missing owner ref for the member: $m") + // check that all blocks in a conditional chain share the same owner + case cb: DFConditional.Block => + cb.prevBlockOrHeaderRef.get match + case prevBlock: DFConditional.Block if prevBlock.getOwner != cb.getOwner => + reportViolation( + s"""|Conditional block chain has mismatched owners. + |Block: $cb + |Owner: ${cb.getOwner} + |Prev block: $prevBlock + |Prev owner: ${prevBlock.getOwner}""".stripMargin + ) + case _ => // check by-name selectors case pbns: DFVal.PortByNameSelect => val design = pbns.designInstRef.get // check port existence - getSet.designDB.portsByName(design).get(pbns.portNamePath) match + getSet.designDB.dupPortsByName(design).get(pbns.portNamePath) match case None => reportViolation( s"Missing port ${pbns.portNamePath} for by-name port selection: ${pbns}" @@ -90,6 +102,11 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: s"""|An anonymous value has no references. |Referenced value: $dfVal""".stripMargin ) + case range: DFRange if !skipAnonRefCheck && range.originMembers.isEmpty => + reportViolation( + s"""|An anonymous range has no references. + |Referenced range: $range""".stripMargin + ) case _ => end match } @@ -139,7 +156,8 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: originMember match case originVal: DFVal if originVal.isGlobal => case _: DFVal.DesignParam => - case _ => + case _: DFDesignBlock => // paramMap entries may reference global anon vals + case _ => reportViolation( s"""|A global anonymous member is referenced by a non-global member. |Target member: ${targetVal} @@ -147,6 +165,18 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: ) case _ => } + // check that no DuplicationRef exists in refTable + refTable.foreach { (ref, _) => + if (ref.isInstanceOf[DFRef.DuplicationRef]) + reportViolation(s"DuplicationRef found in refTable: $ref") + } + // check that no member has a DuplicationRef ownerRef + getSet.designDB.members.foreach { member => + if (member.ownerRef.isInstanceOf[DFRef.DuplicationRef]) + reportViolation( + s"Member with DuplicationRef ownerRef found in members: $member" + ) + } require(!hasViolations, "Failed reference check!") end refCheck private def memberExistenceCheck()(using MemberGetSet): Unit = @@ -221,8 +251,10 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: s"The global member ${m.hashString}:\n$m\nHas reference $r pointing to a later member ${rm.hashString}:\n${rm}" ) case _ => + val hierarchy = + if (m.ownerRef.get == DFMember.Empty) "" else m.getOwnerNamed.getFullName println( - s"The member ${m.hashString}:\n$m\nIn hierarchy:\n${m.getOwnerNamed.getFullName}\nHas reference $r pointing to a later member ${rm.hashString}:\n${rm}" + s"The member ${m.hashString}:\n$m\nIn hierarchy:\n$hierarchy\nHas reference $r pointing to a later member ${rm.hashString}:\n${rm}" ) hasViolations = true require(!hasViolations, "Failed member order check!") @@ -234,7 +266,7 @@ case class SanityCheck(skipAnonRefCheck: Boolean) extends Stage: end orderCheck def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = - // refCheck() + refCheck() memberExistenceCheck() ownershipCheck(designDB.top, designDB.membersNoGlobals.drop(1)) orderCheck() diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala index 9ed680225..67b9ebdff 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/SimplifyRTOps.scala @@ -8,19 +8,103 @@ import dfhdl.internals.* import DFVal.Func.Op as FuncOp import scala.collection.mutable //format: off -/** This stage transforms: - * - wait statements into while loops with a cycle wait. For example: - * waitWhile(cond) -> while(cond) 1.cy.wait - * - rising/falling edge operations into reg alias detection operations. For example: - * i.rising -> !i.reg(1, init = 1) && i - * i.falling -> i.reg(1, init = 0) && !i - * - non-zero/one cycle wait statements to while loops with a counter. For example (under 50Mhz clock): - * 50000000.cy.wait -> while(waitCnt != 50000000) 1.cy.wait +/** This stage simplifies RT-domain operations into lower-level constructs that downstream stages + * can process. It handles four kinds of transformations: rising/falling edge predicates, boolean + * wait statements, cycle-count wait statements, and for loops. + * + * ==Rule 1: Rising/falling edge simplification== + * + * `rising` and `falling` predicates on a `Bit` signal are replaced with register-based edge + * detection expressions: + * {{{ + * // Before + * i.rising + * i.falling + * + * // After + * (!i.reg(1, init = 1)) && i + * i.reg(1, init = 0) && (!i) + * }}} + * + * ==Rule 2: Boolean wait → while loop== + * + * `waitWhile(cond)` / `waitUntil(cond)` statements (including those with rising/falling edge + * triggers) are replaced with `while` loops. If the wait has a name, the generated while loop + * takes that name: + * {{{ + * // Before + * waitWhile(i) + * val MyWait = waitUntil(i) + * waitUntil(i.falling) + * + * // After + * while (i) + * end while + * val MyWait = while (!i) + * end MyWait + * while ((!i.reg(1, init = 0)) || i) + * end while + * }}} + * + * ==Rule 3: Cycle-count wait → while loop with counter== + * + * A wait with a cycle count (`N.cy.wait`) is replaced by a `VAR.REG` counter assigned to zero + * before the loop and a `while` loop that counts up to `N-1`. A single-cycle wait (`1.cy.wait`) + * is left unchanged (no loop needed). If the wait has a name (`val N = X.cy.wait`), the counter + * register is prefixed with `N_`: + * {{{ + * // Before + * x.din := 1 + * 50000000.cy.wait + * val MyWait = waitParam.cy.wait + * + * // After + * x.din := 1 + * val waitCnt = UInt(26) <> VAR.REG + * waitCnt.din := d"26'0" + * while (waitCnt != d"26'49999999") + * waitCnt.din := waitCnt + d"26'1" + * end while + * val MyWait_waitCnt = UInt(26) <> VAR.REG + * MyWait_waitCnt.din := d"26'0" + * val MyWait = while (MyWait_waitCnt != (waitParam - d"26'1")) + * MyWait_waitCnt.din := MyWait_waitCnt + d"26'1" + * end MyWait + * }}} + * + * ==Rule 4: For loop → while loop with iterator== + * + * `for` loops that are inside an RT process are replaced by a `Int <> VAR.REG` iterator assigned + * to the range start before the loop, and a `while` loop whose body ends with the iterator + * increment. The comparison operator is `<` for `until` (exclusive) and `<=` for `to` (inclusive); + * for negative steps the operators are `>` and `>=` respectively. For loops tagged as `COMB_LOOP`, + * outside the RT domain, or outside a process block are left unchanged. + * + * Iterator naming: + * - anonymous `for (i <- ...)`: iterator register keeps the original name (`i`) + * - named `val Name = for (i <- ...)`: iterator register is named `Name_i` + * {{{ + * // Before + * x.din := 1 + * for (i <- 0 until 4) + * x.din := 0 + * x.din := 1 + * + * // After + * x.din := 1 + * val i = Int <> VAR.REG + * i.din := 0 + * while (i < 4) + * x.din := 0 + * i.din := i + 1 + * end while + * x.din := 1 + * }}} */ //format: on case object SimplifyRTOps extends Stage: - def dependencies: List[Stage] = List(DropTimedRTWaits, DFHDLUniqueNames) - def nullifies: Set[Stage] = Set(DropUnreferencedAnons) + def dependencies: List[Stage] = List(DropTimedRTWaits) + def nullifies: Set[Stage] = Set(DropUnreferencedAnons, DFHDLUniqueNames, DropLocalDcls) def transform(designDB: DB)(using MemberGetSet, CompilerOptions): DB = given RefGen = RefGen.fromGetSet @@ -29,7 +113,7 @@ case object SimplifyRTOps extends Stage: case _: Wait => true case _ => false } - val patchList = designDB.members.view.collect { + val patchList = designDB.members.view.flatMap { case trigger @ DFVal.Func( _, op @ (FuncOp.rising | FuncOp.falling), @@ -94,8 +178,11 @@ case object SimplifyRTOps extends Stage: // the bug originates from a conversion from UInt[1] to the iterType. It could be that the // methods under core.DFVal.Alias are not working as intended because of meta-programming. val initZero = dfhdl.core.DFVal.Const(iterType, Some(BigInt(0))) - val waitCnt = - iterType.<>(VAR.REG).initForced(List(initZero))(using dfc.setName("waitCnt")) + val waitCntName = waitMember.meta.nameOpt match + case Some(waitName) => s"${waitName}_waitCnt" + case None => "waitCnt" + val waitCnt = iterType.<>(VAR.REG)(using dfc.setName(waitCntName)) + waitCnt.din := initZero // the upper bound for the while loop count val upperBound = cyclesVal match // the upper bound is reduced to a simpler form when the number of cycles is a constant anonymous value @@ -112,7 +199,65 @@ case object SimplifyRTOps extends Stage: Some(dsn.patch) else None end if - }.flatten.toList + + // replace RT for loops with while loops + iterator VAR.REG + increment at end of body + case forBlock: DFLoop.DFForBlock + if forBlock.isInRTDomain && !forBlock.isCombinational && forBlock.isInProcess => + val iteratorDcl = forBlock.iteratorRef.get + val range = forBlock.rangeRef.get + val startBigInt: BigInt = range.startRef.get match + case DFVal.Const(data = Some(v: BigInt)) => v + case _ => BigInt(0) + val stepBigInt: BigInt = range.stepRef.get match + case DFVal.Const(data = Some(v: BigInt)) => v + case _ => BigInt(1) + val endValIR = range.endRef.get + val rangeOp = range.op + val iterName = forBlock.meta.nameOpt match + case None => iteratorDcl.getName + case Some(name) => s"${name}_${iteratorDcl.getName}" + val forBodyMembers = forBlock.members(MemberView.Folded) + // M1: create iterator VAR.REG + guard + whileBlock, replacing forBlock. + // ReplaceWithLast(ChangeRefAndRemove) makes whileBlock (last M1 member) replace forBlock, + // redirecting ALL refs to forBlock (including body members' ownerRefs) to whileBlock. + val m1 = new MetaDesign( + forBlock, + Patch.Add.Config.ReplaceWithLast(Patch.Replace.Config.ChangeRefAndRemove), + dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) + ): + val startConst = dfhdl.core.DFVal.Const(dfhdl.core.DFInt32, Some(startBigInt)) + val newIterDcl = dfhdl.core.DFInt32.<>(VAR.REG)(using dfc.setName(iterName)) + newIterDcl.din := startConst + val endVal = endValIR.asValOf[dfhdl.core.DFInt32] + val guard = (rangeOp, stepBigInt.signum) match + case (DFRange.Op.Until, s) if s >= 0 => newIterDcl < endVal + case (DFRange.Op.To, s) if s >= 0 => newIterDcl <= endVal + case (DFRange.Op.Until, _) => newIterDcl > endVal + case (DFRange.Op.To, _) => newIterDcl >= endVal + val whileBlock = dfhdl.core.DFWhile.Block(guard)(using dfc.setMeta(forBlock.meta)) + dfc.enterOwner(whileBlock) + dfc.exitOwner() + val newIterDclIR = m1.newIterDcl.asIR + val iterDclPatch = + iteratorDcl -> Patch.Replace(newIterDclIR, Patch.Replace.Config.ChangeRefAndRemove) + if forBodyMembers.nonEmpty then + // M2: create the increment (newIterDcl.din := newIterDcl + step) after the last body member. + // M2's injectedOwner = forBodyMembers.last.getOwner = forBlock (in original DB). + // After M1's ref redirect (forBlock → whileBlock), M2's members' ownerRefs are also + // redirected to whileBlock, placing the increment correctly as the last while-body statement. + val m2 = new MetaDesign( + forBodyMembers.last, + Patch.Add.Config.After, + dfhdl.core.DomainType.RT(dfhdl.core.RTDomainCfg.Derived) + ): + val stepConst = dfhdl.core.DFVal.Const(dfhdl.core.DFInt32, Some(stepBigInt)) + m1.newIterDcl.din := m1.newIterDcl + stepConst + List(m1.patch, iterDclPatch, m2.patch) + else List(m1.patch, iterDclPatch) + end if + + case _ => None + }.toList designDB.patch(patchList) end transform diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala index 530bb9664..e18df5e26 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ToED.scala @@ -11,7 +11,7 @@ import scala.annotation.tailrec import scala.collection.mutable case object ToED extends Stage: def dependencies: List[Stage] = - List(DropUnreferencedAnons, ToRT, NameRegAliases, ExplicitNamedVars, AddClkRst, + List(DropUnreferencedAnons, ToRT, DropRTProcess, NameRegAliases, ExplicitNamedVars, AddClkRst, SimpleOrderMembers) def nullifies: Set[Stage] = Set(DropUnreferencedAnons) def transform(designDB: DB)(using getSet: MemberGetSet, co: CompilerOptions): DB = @@ -54,12 +54,12 @@ case object ToED extends Stage: case _ => } def collectFilter(member: DFMember): Boolean = member match - case IteratorDcl() => true - case _: DFVal.Dcl => false - case _: DFVal.DesignParam => false - case _: DFOwnerNamed => false - case dfVal: DFVal if dfVal.isReferencedByAnyDcl => false - case _ => true + case IteratorDcl() => true + case _: DFVal.Dcl => false + case _: DFVal.DesignParam => false + case _: DFOwnerNamed => false + case dfVal: DFVal if dfVal.isReferencedByAnyDclOrDesign => false + case _ => true def getProcessAllMembers(list: List[DFMember]): List[DFMember] = val processBlockAllMembersSet: Set[DFMember] = list.view.flatMap { @@ -160,11 +160,12 @@ case object ToED extends Stage: }.toList // create a combinational process if needed val hasProcessAll = - !domainIsPureSequential && (dclChangeList.nonEmpty || processBlockAllMembers.exists { - case net: DFNet => true - case ch: DFConditional.Header => true - case _ => false - }) + !domainIsPureSequential && + (dclChangeList.nonEmpty || processBlockAllMembers.exists { + case net: DFNet => true + case ch: DFConditional.Header => true + case _ => false + }) if (hasProcessAll) process(all) { val inVHDL = co.backend.isVHDL @@ -256,7 +257,9 @@ case object ToED extends Stage: block ) val hasSeqProcess = - clkCfg != None && (dclREGList.nonEmpty || processBlockAllMembers.nonEmpty && domainIsPureSequential) + clkCfg != None && + (dclREGList.nonEmpty || + processBlockAllMembers.nonEmpty && domainIsPureSequential) if (hasSeqProcess) if (rstCfg != None) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ViaConnection.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ViaConnection.scala index a2bc18ad0..78f24cc94 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/ViaConnection.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/ViaConnection.scala @@ -15,7 +15,8 @@ case object ViaConnection extends Stage: case (ib, members) if !ib.isTop => // getting only ports that are not already connected to variables val (ports, nets): (List[DFVal.Dcl], List[DFNet]) = - members.foldRight((List.empty[DFVal.Dcl], List.empty[DFNet])) { + val ports = designDB.dupPortsByName(ib).values + ports.foldRight((List.empty[DFVal.Dcl], List.empty[DFNet])) { case (p @ DclOut(), (ports, nets)) => val conns = p.getConnectionsFrom conns.headOption match @@ -43,6 +44,7 @@ case object ViaConnection extends Stage: case _ => (p :: ports, nets) case (_, x) => x } + end val extension (port: DFVal.Dcl) // set reachable type parameters for the selected ports by setting the MetaDesign context's diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogDataPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogDataPrinter.scala index 6f4b5b875..2e27a6ed0 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogDataPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogDataPrinter.scala @@ -42,9 +42,11 @@ protected trait VerilogDataPrinter extends AbstractDataPrinter: def csDFSIntFormatBig(value: BigInt, width: IntParamRef): String = val csWidth = width.refCodeString.applyBrackets() if (width.isRef) - if (value.isValidInt && allowWidthCastSyntax) s"""${csWidth}'($value)""" + val actualWidth = value.bitsWidth(true) + if (value.isValidInt && allowWidthCastSyntax) + if (value >= 0) s"""${csWidth}'($actualWidth'sd$value)""" + else s"${csWidth}'(-$actualWidth'sd${-value})" else - val actualWidth = value.bitsWidth(true) if (allowWidthCastSyntax && printer.allowSignedKeywordAndOps) if (value >= 0) s"""${csWidth}'($actualWidth'sd$value)""" else s"""${csWidth}'(-$actualWidth'sd${-value})""" diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala index 31824d82a..f4bf32d42 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogOwnerPrinter.scala @@ -13,7 +13,12 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: type TPrinter <: VerilogPrinter val useStdSimLibrary: Boolean = true def fileSuffix = "v" - def defsName: String = s"${getSet.topName}_defs" + def defsName: String = + val name = printerOptions.globalDefsFileName + if (name.nonEmpty) + val dotIdx = name.lastIndexOf('.') + if (dotIdx > 0) name.substring(0, dotIdx) else name + else s"${getSet.topName}_defs" def csLibrary(inSimulation: Boolean, minTimeUnitOpt: Option[TimeNumber.Unit]): String = val csTimeScale = minTimeUnitOpt.map { unit => def unitToStr(unit: TimeNumber.Unit): String = @@ -24,9 +29,9 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: val precisionUnit = unitToStr(TimeNumber(1e-3, unit).normalize.unit) s"`timescale 1${scaleUnit}/1${precisionUnit}" }.getOrElse(s"`timescale 1ns/1ps") - s"""`default_nettype none - |$csTimeScale - |`include "${printer.globalFileName}"""".stripMargin + sn"""|`default_nettype none + |$csTimeScale + |${if (printer.hasGlobalContent) s"""`include "${printer.globalFileName}"""" else ""}""" def moduleName(design: DFDesignBlock): String = design.dclName val parameterizedModuleSupport: Boolean = printer.dialect match @@ -105,9 +110,9 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: ) val designParamList = designMembers.collect { case param: DesignParam => val defaultValue = - if (design.isTop) s" = ${param.dfValRef.refCodeString}" + if (design.isTop) s" = ${param.appliedOrDefaultValRef.refCodeString}" else - param.defaultRef.get match + param.defaultValRef.get match case DFMember.Empty => // missing default values are supported if (noDefaultParamSupport) "" @@ -115,7 +120,7 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: // (different instances may have different constant data, but for default, // a single module description can have any valid data, just to satisfy the standard) else s" = ${printer.csConstData(param.dfType, param.getConstData.get)}" - case _ => s" = ${param.defaultRef.refCodeString}" + case _ => s" = ${param.defaultValRef.refCodeString}" val csType = printer.csDFType(param.dfType).emptyOr(_ + " ") val csTypeNoLogic = if (printer.supportLogicType) csType else csType.replace("logic ", "") s"parameter ${csTypeNoLogic}${param.getName}$defaultValue" @@ -125,7 +130,8 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: else if (designParamList.length == 1) designParamList.mkString("#(", ", ", ")") else "#(" + designParamList.mkString("\n", ",\n", "\n").hindent(2) + ")" val includeModuleDefs = - if (printer.allowTypeDef) "" else s"""`include "${printer.globalFileName}"""" + if (printer.allowTypeDef || !printer.hasGlobalContent) "" + else s"""`include "${printer.globalFileName}"""" // include parameter definitions only when parameters are used in the design val paramDefines = if (printer.supportGlobalParameters) "" @@ -162,9 +168,9 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: |""".stripMargin def csDFDesignBlockInst(design: DFDesignBlock): String = val body = csDFDesignLateBody(design) - val designParamList = design.members(MemberView.Folded).collect { case param: DesignParam => - s".${param.getName} (${param.dfValRef.refCodeString})" - } + val designParamList = design.paramMap.view.map { (name, ref) => + s".${name} (${ref.refCodeString})" + }.toList val designParamCS = if (designParamList.isEmpty || design.isVendorIPBlackbox) "" else " #(" + designParamList.mkString("\n", ",\n", "\n").hindent(1) + ")" @@ -189,13 +195,18 @@ protected trait VerilogOwnerPrinter extends AbstractOwnerPrinter: def csDFCaseKeyword: String = "" def csDFCaseSeparator: String = ":" def csDFCaseGuard(guardRef: DFConditional.Block.GuardRef): String = printer.unsupported - def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean): String = + def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean, isUnique: Boolean): String = val insideSupport = printer.dialect match case VerilogDialect.v2001 | VerilogDialect.v95 => false case _ => true + val uniqueSupport = printer.dialect match + case VerilogDialect.v2001 | VerilogDialect.v95 => false + case _ => true + val uniquePrefix = + if (isUnique && uniqueSupport) "unique " else "" val keyWord = if (wildcardSupport && !insideSupport) "casez" else "case" val insideStr = if (wildcardSupport && insideSupport) " inside" else "" - s"$keyWord ($csSelector)$insideStr" + s"$uniquePrefix$keyWord ($csSelector)$insideStr" def csDFMatchEnd: String = "endcase" val sensitivityListSep = printer.dialect match diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogPrinter.scala index aa62a32bb..a9caa0a1f 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogPrinter.scala @@ -171,32 +171,36 @@ class VerilogPrinter(val dialect: VerilogDialect)(using case VerilogDialect.v2001 | VerilogDialect.v95 => "vh" case _ => "svh" def globalFileName: String = - s"${printer.defsName}.$verilogFileHeaderSuffix" + val name = printerOptions.globalDefsFileName + if (name.nonEmpty && name.contains('.')) name + else s"${printer.defsName}.$verilogFileHeaderSuffix" override def csGlobalFileContent: String = - val defName = printer.defsName.toUpperCase - // the module defs are alternating between outside of and inside of the module - // because we will include the module defs twice, once in the top of the file - // and second time inside the module. - val globalParams = - if (printer.supportGlobalParameters) super.csGlobalFileContent else "" - val globalToLocalParams = - if (printer.supportGlobalParameters) "" else super.csGlobalFileContent - val moduleDefs = - if (printer.allowTypeDef) "" - else - sn"""|`ifndef ${defName}_MODULE - |`define ${defName}_MODULE - |`else - |$globalToLocalParams - |${printer.csGlobalTypeFuncDcls} - |`undef ${defName}_MODULE - |`endif""" - sn"""|`ifndef $defName - |`define $defName - |$globalParams - |`endif - |$moduleDefs - |""" + if (hasGlobalContent) + val defName = printer.defsName.toUpperCase + // the module defs are alternating between outside of and inside of the module + // because we will include the module defs twice, once in the top of the file + // and second time inside the module. + val globalParams = + if (printer.supportGlobalParameters) super.csGlobalFileContent else "" + val globalToLocalParams = + if (printer.supportGlobalParameters) "" else super.csGlobalFileContent + val moduleDefs = + if (printer.allowTypeDef) "" + else + sn"""|`ifndef ${defName}_MODULE + |`define ${defName}_MODULE + |`else + |$globalToLocalParams + |${printer.csGlobalTypeFuncDcls} + |`undef ${defName}_MODULE + |`endif""" + sn"""|`ifndef $defName + |`define $defName + |$globalParams + |`endif + |$moduleDefs + |""" + else "" end csGlobalFileContent def dfhdlDefsFileName: String = s"dfhdl_defs.$verilogFileHeaderSuffix" def dfhdlSourceContents: String = diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogTypePrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogTypePrinter.scala index 345cc726b..d97221539 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogTypePrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogTypePrinter.scala @@ -74,11 +74,8 @@ protected trait VerilogTypePrinter extends AbstractTypePrinter: dfType.entries.view .map((n, v) => s"${enumName}_$n = $v") .mkString(",\n") - // TODO: quartus seems to not accept an explicit size, so we drop it locally where it's not required. - // Globally, size is required (at least for verilator linter), so we need to drop enumeration altogether - // in such a case (change to a vector and list of constants) and then remove the special case handling - // here. - val explicitWidth = if (global) s" logic [${dfType.width - 1}:0]" else "" + // TODO: quartus seems to not accept an explicit size Globally + val explicitWidth = s" logic [${dfType.width - 1}:0]" s"typedef enum$explicitWidth {\n${entries.hindent}\n} ${csDFEnumTypeName(dfType)};" else dfType.entries.view diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala index 4f0221fb9..ce5a9bc1e 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/verilog/VerilogValPrinter.scala @@ -34,7 +34,7 @@ protected trait VerilogValPrinter extends AbstractValPrinter: // using the constant data only happens in verilog.v95, since parameters are declared in // the body and must have defaults. case param: DesignParam => - param.defaultRef.get match + param.defaultValRef.get match case defaultVal: CanBeExpr if !param.getOwnerDesign.isTop => csDFValExpr(defaultVal) case _ => printer.csConstData(param.dfType, param.getConstData.get) case _ => csDFValExpr(dfVal) @@ -85,7 +85,8 @@ protected trait VerilogValPrinter extends AbstractValPrinter: val cellWidth = dfType.cellType.width val length = dfType.cellDimParamRefs.head.getInt val ret = for (i <- 0 until length) - yield s"${dfVal.getName}[$i] = ${initVal.getName}[${(length - i) * cellWidth - 1}:${(length - i) * cellWidth - cellWidth}];" + yield s"${dfVal.getName}[$i] = ${initVal.getName}[${(length - i) * cellWidth - + 1}:${(length - i) * cellWidth - cellWidth}];" ret.mkString("\n") case Func(op = Func.Op.++, args = args) => args.view.zipWithIndex @@ -148,7 +149,9 @@ protected trait VerilogValPrinter extends AbstractValPrinter: argL.get.dfType match case DFSInt(_) => ">>>" case _ => ">>" - case _ => commonOpStr + case Func.Op.| if argL.get.dfType == DFBool => "||" + case Func.Op.& if argL.get.dfType == DFBool => "&&" + case _ => commonOpStr (argL.get.dfType, dfVal.op) match case ( DFSInt(widthRef), @@ -319,9 +322,14 @@ protected trait VerilogValPrinter extends AbstractValPrinter: List.tabulate(vecLength)(i => s"$relValStr[${relHighIdx - i * cellWidth}:${relHighIdx - (i + 1) * cellWidth + 1}]" ).csList(literalGroupOpen, ",", "}") + case _: DFBoolOrBit => + List.tabulate(vecLength)(i => + s"$relValStr[${relHighIdx - i}]" + ).csList(literalGroupOpen, ",", "}") case x => println(x) printer.unsupported + end match end to_vector_conv to_vector_conv(toVector, toVector.width - 1) case (DFBits(Int(tWidth)), fromVector: DFVector) => @@ -332,7 +340,8 @@ protected trait VerilogValPrinter extends AbstractValPrinter: List.tabulate(vecLength)(i => from_vector_conv(cellType, s"[$i]")) .csList("{", ",", "}") case cellType: DFBits => - val cellWidth = cellType.width + List.tabulate(vecLength)(i => s"$relValStr$prevSelect[$i]").csList("{", ",", "}") + case _: DFBoolOrBit => List.tabulate(vecLength)(i => s"$relValStr$prevSelect[$i]").csList("{", ",", "}") case x => println(x) diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala index 0440284c0..18babf197 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLOwnerPrinter.scala @@ -12,14 +12,20 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: type TPrinter <: VHDLPrinter val useStdSimLibrary: Boolean = true def fileSuffix = "vhdl" - def packageName: String = s"${getSet.topName}_pkg" + def packageName: String = + val name = printerOptions.globalDefsFileName + if (name.nonEmpty) + val dotIdx = name.lastIndexOf('.') + if (dotIdx > 0) name.substring(0, dotIdx) else name + else s"${getSet.topName}_pkg" def csLibrary(inSimulation: Boolean, usesMathReal: Boolean): String = val default = - s"""library ieee; - |use ieee.std_logic_1164.all; - |use ieee.numeric_std.all;${if (usesMathReal) "\nuse ieee.math_real.all;" else ""} - |use work.dfhdl_pkg.all; - |use work.$packageName.all;""".stripMargin + sn"""|library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |${if (usesMathReal) "use ieee.math_real.all;" else ""} + |use work.dfhdl_pkg.all; + |${if (printer.hasGlobalContent) s"use work.$packageName.all;" else ""}""" if (useStdSimLibrary && inSimulation) s"""$default | @@ -36,11 +42,11 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: .mkString(";\n") val designParamList = designMembers.collect { case param: DesignParam => val defaultValue = - if (design.isTop) s" := ${param.dfValRef.refCodeString}" + if (design.isTop) s" := ${param.appliedOrDefaultValRef.refCodeString}" else - param.defaultRef.get match + param.defaultValRef.get match case DFMember.Empty => "" - case _ => s" := ${param.defaultRef.refCodeString}" + case _ => s" := ${param.defaultValRef.refCodeString}" s"${param.getName} : ${printer.csDFType(param.dfType)}$defaultValue" } val genericBlock = @@ -177,9 +183,9 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: end csDFDesignBlockDcl def csDFDesignBlockInst(design: DFDesignBlock): String = val body = csDFDesignLateBody(design) - val designParamList = design.members(MemberView.Folded).collect { case param: DesignParam => - s"${param.getName} => ${param.dfValRef.refCodeString}" - } + val designParamList = design.paramMap.view.map { (name, ref) => + s"${name} => ${ref.refCodeString}" + }.toList val designParamCS = if (designParamList.isEmpty || design.isVendorIPBlackbox) "" else " generic map (" + designParamList.mkString("\n", ",\n", "\n").hindent(1) + ")" @@ -211,7 +217,7 @@ protected trait VHDLOwnerPrinter extends AbstractOwnerPrinter: def csDFCaseKeyword: String = "when " def csDFCaseSeparator: String = " =>" def csDFCaseGuard(guardRef: DFConditional.Block.GuardRef): String = printer.unsupported - def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean): String = + def csDFMatchStatement(csSelector: String, wildcardSupport: Boolean, isUnique: Boolean): String = s"case $csSelector is" def csDFMatchEnd: String = "end case;" def csProcessBlock(pb: ProcessBlock): String = diff --git a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLPrinter.scala b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLPrinter.scala index 1bd8fcc18..d16fd5388 100644 --- a/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLPrinter.scala +++ b/compiler/stages/src/main/scala/dfhdl/compiler/stages/vhdl/VHDLPrinter.scala @@ -154,72 +154,79 @@ class VHDLPrinter(val dialect: VHDLDialect)(using def csDocString(doc: String): String = doc.linesIterator.mkString("--", "\n--", "") def csAnnotations(annotations: List[annotation.HWAnnotation]): String = "" // def csTimer(timer: Timer): String = unsupported - def globalFileName: String = s"${printer.packageName}.vhd" + def globalFileName: String = + val name = printerOptions.globalDefsFileName + if (name.nonEmpty && name.contains('.')) name + else s"${printer.packageName}.vhd" def designFileName(designName: String): String = s"$designName.vhd" def dfhdlDefsFileName: String = s"dfhdl_pkg.vhd" def dfhdlSourceContents: String = scala.io.Source.fromResource(dfhdlDefsFileName).getLines().mkString("\n") + override protected def hasGlobalContentCheck: Boolean = + super.hasGlobalContentCheck || printer.globalVectorTypes.nonEmpty override def csGlobalFileContent: String = - // In VHDL the vectors need to be named, and put in dependency order of other named types. - // So first we prepare the vector type declarations in a mutable map and later we remove - // entries that were already placed in the final type printing. - val vectorTypeDcls = mutable.Map.from( - printer.globalVectorTypes.view.map { case (tpName, (vecType, depth)) => - tpName -> printer.csDFVectorDclsGlobal(DclScope.Pkg)(tpName, vecType, depth) - } - ) - // The body declarations can be in any order, as long as it's consistent between compilations. - val vectorTypeDclsBody = - printer.globalVectorTypes.view.map { case (tpName, (vecType, depth)) => - printer.csDFVectorDclsGlobal(DclScope.PkgBody)(tpName, vecType, depth) - }.mkString("\n") - // collect the global named types, including vectors - val namedDFTypes = ListSet.from(getSet.designDB.members.view.collect { - case port @ DclPort() => port.dfType - case const @ DclConst() if const.isGlobal => const.dfType - }.flatMap(_.decompose { case dt: (DFVector | NamedDFType) => dt })) - // declarations of the types and relevant functions - val namedTypeConvFuncsDcl = namedDFTypes.view - .flatMap { - // vector types can have different dimensions, but we only need the declaration once - case dfType: DFVector => - val tpName = printer.getVecDepthAndCellTypeName(dfType)._1 - vectorTypeDcls.get(tpName) match - case Some(desc) => - vectorTypeDcls -= tpName - Some(desc) - case None => None - case dfType: NamedDFType => - List( - printer.csNamedDFTypeDcl(dfType, global = true), - printer.csNamedDFTypeConvFuncsDcl(dfType) - ) - } - .mkString("\n") - val namedTypeConvFuncsBody = - getSet.designDB.getGlobalNamedDFTypes.view - .collect { case dfType: NamedDFType => printer.csNamedDFTypeConvFuncsBody(dfType) } + if (hasGlobalContent) + // In VHDL the vectors need to be named, and put in dependency order of other named types. + // So first we prepare the vector type declarations in a mutable map and later we remove + // entries that were already placed in the final type printing. + val vectorTypeDcls = mutable.Map.from( + printer.globalVectorTypes.view.map { case (tpName, (vecType, depth)) => + tpName -> printer.csDFVectorDclsGlobal(DclScope.Pkg)(tpName, vecType, depth) + } + ) + // The body declarations can be in any order, as long as it's consistent between compilations. + val vectorTypeDclsBody = + printer.globalVectorTypes.view.map { case (tpName, (vecType, depth)) => + printer.csDFVectorDclsGlobal(DclScope.PkgBody)(tpName, vecType, depth) + }.mkString("\n") + // collect the global named types, including vectors + val namedDFTypes = ListSet.from(getSet.designDB.members.view.collect { + case port @ DclPort() => port.dfType + case const @ DclConst() if const.isGlobal => const.dfType + }.flatMap(_.decompose { case dt: (DFVector | NamedDFType) => dt })) + // declarations of the types and relevant functions + val namedTypeConvFuncsDcl = namedDFTypes.view + .flatMap { + // vector types can have different dimensions, but we only need the declaration once + case dfType: DFVector => + val tpName = printer.getVecDepthAndCellTypeName(dfType)._1 + vectorTypeDcls.get(tpName) match + case Some(desc) => + vectorTypeDcls -= tpName + Some(desc) + case None => None + case dfType: NamedDFType => + List( + printer.csNamedDFTypeDcl(dfType, global = true), + printer.csNamedDFTypeConvFuncsDcl(dfType) + ) + } .mkString("\n") - val usesMathReal = getSet.designDB.membersGlobals.exists { - _.dfType.decompose { case dt: DFDouble => dt }.nonEmpty - } - sn"""|library ieee; - |use ieee.std_logic_1164.all; - |use ieee.numeric_std.all; - |${if (usesMathReal) "use ieee.math_real.all;" else ""} - |use work.dfhdl_pkg.all; - | - |package ${printer.packageName} is - |${csGlobalConstIntDcls} - |${namedTypeConvFuncsDcl} - |${csGlobalConstNonIntDcls} - |end package ${printer.packageName}; - | - |package body ${printer.packageName} is - |${namedTypeConvFuncsBody} - |${vectorTypeDclsBody} - |end package body ${printer.packageName}; - |""" + val namedTypeConvFuncsBody = + getSet.designDB.getGlobalNamedDFTypes.view + .collect { case dfType: NamedDFType => printer.csNamedDFTypeConvFuncsBody(dfType) } + .mkString("\n") + val usesMathReal = getSet.designDB.membersGlobals.exists { + _.dfType.decompose { case dt: DFDouble => dt }.nonEmpty + } + sn"""|library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |${if (usesMathReal) "use ieee.math_real.all;" else ""} + |use work.dfhdl_pkg.all; + | + |package ${printer.packageName} is + |${csGlobalConstIntDcls} + |${namedTypeConvFuncsDcl} + |${csGlobalConstNonIntDcls} + |end package ${printer.packageName}; + | + |package body ${printer.packageName} is + |${namedTypeConvFuncsBody} + |${vectorTypeDclsBody} + |end package body ${printer.packageName}; + |""" + else "" end csGlobalFileContent def alignCode(cs: String): String = cs diff --git a/compiler/stages/src/test/scala/StagesSpec/DropDesignParamDepsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropDesignParamDepsSpec.scala index 91dd8708d..49b4db535 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropDesignParamDepsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropDesignParamDepsSpec.scala @@ -124,10 +124,7 @@ class DropDesignParamDepsSpec extends StageSpec: | val inner_depth: Int <> CONST = baseWidth + 1 | val x = Bits(baseWidth) <> IN | val y = Bits(baseWidth) <> OUT - | val inner = Inner( - | width = baseWidth, - | depth = inner_depth - | ) + | val inner = Inner(width = baseWidth) | inner.x <> x | y <> inner.y.resize(baseWidth) |end Outer diff --git a/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala index 7f232682c..9a5abf749 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropRTProcessSpec.scala @@ -11,15 +11,15 @@ class DropRTProcessSpec extends StageSpec(): val my_fsm = process: def S0: Step = y.din := 0 - if (x) NextStep else S0 + if (x) S1 else S0 def S1: Step = def onEntry = y.din := 1 - if (x) S2 else FirstStep + if (x) S2 else S0 def S2: Step = def onExit = y.din := 0 - if (x) ThisStep else FirstStep + if (x) S2 else S0 end Foo val top = (new Foo).dropRTProcess assertCodeString( @@ -54,6 +54,43 @@ class DropRTProcessSpec extends StageSpec(): |end Foo""".stripMargin ) } + test("onEntry is not fired on self-transition") { + class Foo extends RTDesign: + val x = Bit <> IN + val y = Bit <> OUT.REG init 0 + process: + def S0: Step = + if (x) S1 else S0 + def S1: Step = + def onEntry = + y.din := 1 + if (x) S1 else S0 + end Foo + val top = (new Foo).dropRTProcess + assertCodeString( + top, + """|class Foo extends RTDesign: + | enum State(val value: UInt[1] <> CONST) extends Encoded.Manual(1): + | case S0 extends State(d"1'0") + | case S1 extends State(d"1'1") + | + | val x = Bit <> IN + | val y = Bit <> OUT.REG init 0 + | val state = State <> VAR.REG init State.S0 + | state match + | case State.S0 => + | if (x) + | y.din := 1 + | state.din := State.S1 + | else state.din := State.S0 + | end if + | case State.S1 => + | if (x) state.din := State.S1 + | else state.din := State.S0 + | end match + |end Foo""".stripMargin + ) + } test("unnamed FSM steps") { class Foo extends RTDesign: val x = Bit <> IN @@ -98,20 +135,186 @@ class DropRTProcessSpec extends StageSpec(): |end Foo""".stripMargin ) } - test("process with no steps") { + test("process with a single step") { + class Foo extends RTDesign: + val x = Bit <> IN + val y = Bit <> OUT.REG init 0 + process: + def S0: Step = + y.din := 0 + S0 + end S0 + end Foo + val top = (new Foo).dropRTProcess + assertCodeString( + top, + """|class Foo extends RTDesign: + | enum State(val value: UInt[1] <> CONST) extends Encoded.Manual(1): + | case S0 extends State(d"1'0") + | + | val x = Bit <> IN + | val y = Bit <> OUT.REG init 0 + | val state = State <> VAR.REG init State.S0 + | state match + | case State.S0 => + | y.din := 0 + | state.din := State.S0 + | end match + |end Foo""".stripMargin + ) + } + test("fall-through steps") { + class Foo extends RTDesign: + val x = Bit <> IN + val y = Bit <> OUT.REG init 0 + process: + def S0: Step = + y.din := 0 + S1 + def S1: Step = + def fallThrough = x + def onEntry = + y.din := 1 + S2 + def S2: Step = + def fallThrough = !x + def onEntry = + y.din := !y + S3 + def S3: Step = + def fallThrough = x ^ x.reg(1, init = 0) + def onEntry = + y.din := y ^ y.reg + S4 + def S4: Step = + y.din := 0 + if (x) S2 else S0 + end Foo + val top = (new Foo).dropRTProcess + assertCodeString( + top, + """|class Foo extends RTDesign: + | enum State(val value: UInt[3] <> CONST) extends Encoded.Manual(3): + | case S0 extends State(d"3'0") + | case S1 extends State(d"3'1") + | case S2 extends State(d"3'2") + | case S3 extends State(d"3'3") + | case S4 extends State(d"3'4") + | + | val x = Bit <> IN + | val y = Bit <> OUT.REG init 0 + | val state = State <> VAR.REG init State.S0 + | state match + | case State.S0 => + | y.din := 0 + | y.din := 1 + | state.din := State.S1 + | if (x) + | y.din := !y + | state.din := State.S2 + | if (!x) + | y.din := y ^ y.reg + | state.din := State.S3 + | if (x ^ x.reg(1, init = 0)) state.din := State.S4 + | end if + | end if + | case State.S1 => + | y.din := !y + | state.din := State.S2 + | if (!x) + | y.din := y ^ y.reg + | state.din := State.S3 + | if (x ^ x.reg(1, init = 0)) state.din := State.S4 + | end if + | case State.S2 => + | y.din := y ^ y.reg + | state.din := State.S3 + | if (x ^ x.reg(1, init = 0)) state.din := State.S4 + | case State.S3 => state.din := State.S4 + | case State.S4 => + | y.din := 0 + | if (x) + | y.din := !y + | state.din := State.S2 + | if (!x) + | y.din := y ^ y.reg + | state.din := State.S3 + | if (x ^ x.reg(1, init = 0)) state.din := State.S4 + | end if + | else state.din := State.S0 + | end if + | end match + |end Foo""".stripMargin + ) + } + test("circular fall-through steps") { class Foo extends RTDesign: val x = Bit <> IN val y = Bit <> OUT.REG init 0 process: - y.din := 0 + def S0: Step = + def fallThrough = x + def onEntry = + y.din := y + S1 + def S1: Step = + def fallThrough = !x + def onEntry = + y.din := !y + S2 + def S2: Step = + def fallThrough = x ^ x.reg(1, init = 0) + def onEntry = + y.din := y ^ y.reg + S0 end Foo val top = (new Foo).dropRTProcess assertCodeString( top, """|class Foo extends RTDesign: + | enum State(val value: UInt[2] <> CONST) extends Encoded.Manual(2): + | case S0 extends State(d"2'0") + | case S1 extends State(d"2'1") + | case S2 extends State(d"2'2") + | | val x = Bit <> IN | val y = Bit <> OUT.REG init 0 - | y.din := 0 + | val state = State <> VAR.REG init State.S0 + | state match + | case State.S0 => + | y.din := !y + | state.din := State.S1 + | if (!x) + | y.din := y ^ y.reg + | state.din := State.S2 + | if (x ^ x.reg(1, init = 0)) + | y.din := y + | state.din := State.S0 + | end if + | end if + | case State.S1 => + | y.din := y ^ y.reg + | state.din := State.S2 + | if (x ^ x.reg(1, init = 0)) + | y.din := y + | state.din := State.S0 + | if (x) + | y.din := !y + | state.din := State.S1 + | end if + | end if + | case State.S2 => + | y.din := y + | state.din := State.S0 + | if (x) + | y.din := !y + | state.din := State.S1 + | if (!x) + | y.din := y ^ y.reg + | state.din := State.S2 + | end if + | end if + | end match |end Foo""".stripMargin ) } diff --git a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala index b2b5ce929..7a93455c7 100644 --- a/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/DropRTWaitsSpec.scala @@ -4,13 +4,49 @@ import dfhdl.* import dfhdl.compiler.stages.dropRTWaits class DropRTWaitsSpec extends StageSpec(): - test("basic single cycle wait") { + test("empty RT process block") { + class Foo extends RTDesign: + process {} + end Foo + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | process: + | def S_0: Step = + | NextStep + | end S_0 + |end Foo""".stripMargin + ) + } + test("single statement in process block") { + class Foo extends RTDesign: + val i = Bit <> IN + val x = Bit <> OUT.REG + process: + x.din := i + end Foo + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Bit <> IN + | val x = Bit <> OUT.REG + | process: + | def S_0: Step = + | NextStep + | end S_0 + | x.din := i + |end Foo""".stripMargin + ) + } + test("basic single cycle wait before assignment") { class Foo extends RTDesign: val i = Bit <> IN val x = Bit <> OUT.REG process: - x.din := 1 1.cy.wait + x.din := i end Foo val top = (new Foo).dropRTWaits assertCodeString( @@ -19,7 +55,32 @@ class DropRTWaitsSpec extends StageSpec(): | val i = Bit <> IN | val x = Bit <> OUT.REG | process: - | x.din := 1 + | def S_0: Step = + | NextStep + | end S_0 + | x.din := i + |end Foo""".stripMargin + ) + } + test("basic single cycle wait after assignment") { + class Foo extends RTDesign: + val i = Bit <> IN + val x = Bit <> OUT.REG + process: + x.din := i + 1.cy.wait + end Foo + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Bit <> IN + | val x = Bit <> OUT.REG + | process: + | def S_0: Step = + | NextStep + | end S_0 + | x.din := i | def S_1: Step = | NextStep | end S_1 @@ -32,6 +93,7 @@ class DropRTWaitsSpec extends StageSpec(): val x = Bit <> OUT.REG process: x.din := 1 + x.din := i 1.cy.wait x.din := !x 1.cy.wait @@ -47,7 +109,11 @@ class DropRTWaitsSpec extends StageSpec(): | val i = Bit <> IN | val x = Bit <> OUT.REG | process: + | def S_0: Step = + | NextStep + | end S_0 | x.din := 1 + | x.din := i | def S_1: Step = | NextStep | end S_1 @@ -83,13 +149,12 @@ class DropRTWaitsSpec extends StageSpec(): | val x = Bit <> OUT.REG | val waitCnt1 = UInt(8) <> VAR.REG init d"8'0" | process: - | def S_1: Step = + | def S_0: Step = | if (waitCnt1 != d"8'149") | waitCnt1.din := waitCnt1 + d"8'1" | ThisStep | else NextStep - | end if - | end S_1 + | end S_0 | waitCnt1.din := d"8'0" | x.din := !x |end Foo""".stripMargin @@ -115,22 +180,22 @@ class DropRTWaitsSpec extends StageSpec(): | val x = Bit <> OUT.REG | val waitCnt1 = UInt(8) <> VAR.REG init d"8'0" | process: - | def S_1: Step = + | def S_0: Step = | if (waitCnt1 != d"8'149") | waitCnt1.din := waitCnt1 + d"8'1" - | def S_1_1: Step = + | def S_0_0: Step = | NextStep - | end S_1_1 - | def S_1_2: Step = + | end S_0_0 + | def S_0_1: Step = | NextStep - | end S_1_2 - | def S_1_3: Step = + | end S_0_1 + | def S_0_2: Step = | NextStep - | end S_1_3 + | end S_0_2 | ThisStep | else NextStep | end if - | end S_1 + | end S_0 | waitCnt1.din := d"8'0" | x.din := !x |end Foo""".stripMargin @@ -159,22 +224,66 @@ class DropRTWaitsSpec extends StageSpec(): | val waitCnt1 = UInt(8) <> VAR.REG init d"8'0" | val waitCnt2 = UInt(8) <> VAR.REG init d"8'0" | process: - | def S_1: Step = + | def S_0: Step = | if (waitCnt1 != d"8'149") | waitCnt1.din := waitCnt1 + d"8'1" | ThisStep | else NextStep - | end if + | end S_0 + | waitCnt1.din := d"8'0" + | x.din := !x + | def S_1: Step = + | if (waitCnt2 != d"8'149") + | waitCnt2.din := waitCnt2 + d"8'1" + | ThisStep + | else NextStep | end S_1 + | waitCnt2.din := d"8'0" + | x.din := 1 + |end Foo""".stripMargin + ) + } + test("basic multiple while loops with fall-through") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG + val waitCnt1 = UInt(8) <> VAR.REG init 0 + val waitCnt2 = UInt(8) <> VAR.REG init 0 + process: + while (waitCnt1 != 149) + FALL_THROUGH + waitCnt1.din := waitCnt1 + 1 + waitCnt1.din := 0 + x.din := !x + while (waitCnt2 != 149) + waitCnt2.din := waitCnt2 + 1 + waitCnt2.din := 0 + x.din := 1 + end Foo + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG + | val waitCnt1 = UInt(8) <> VAR.REG init d"8'0" + | val waitCnt2 = UInt(8) <> VAR.REG init d"8'0" + | process: + | def S_0: Step = + | def fallThrough: Boolean <> VAL = + | !(waitCnt1 != d"8'149") + | end fallThrough + | if (waitCnt1 != d"8'149") + | waitCnt1.din := waitCnt1 + d"8'1" + | ThisStep + | else NextStep + | end S_0 | waitCnt1.din := d"8'0" | x.din := !x - | def S_2: Step = + | def S_1: Step = | if (waitCnt2 != d"8'149") | waitCnt2.din := waitCnt2 + d"8'1" | ThisStep | else NextStep - | end if - | end S_2 + | end S_1 | waitCnt2.din := d"8'0" | x.din := 1 |end Foo""".stripMargin @@ -202,22 +311,21 @@ class DropRTWaitsSpec extends StageSpec(): | val waitCnt1 = UInt(8) <> VAR.REG init d"8'0" | val waitCnt2 = UInt(8) <> VAR.REG init d"8'0" | process: - | def S_1: Step = + | def S_0: Step = | if (waitCnt1 != d"8'149") - | def S_1_1: Step = + | def S_0_0: Step = | if (waitCnt2 != d"8'149") | waitCnt2.din := waitCnt2 + d"8'1" | ThisStep | else NextStep - | end if - | end S_1_1 + | end S_0_0 | waitCnt2.din := d"8'0" | x.din := !x | waitCnt1.din := waitCnt1 + d"8'1" | ThisStep | else NextStep | end if - | end S_1 + | end S_0 | waitCnt1.din := d"8'0" |end Foo""".stripMargin ) @@ -243,6 +351,9 @@ class DropRTWaitsSpec extends StageSpec(): """|class Foo extends RTDesign: | val x = Bit <> OUT.REG init 0 | process: + | def S_0: Step = + | NextStep + | end S_0 | if (x) | x.din := !x | def S_1: Step = @@ -292,6 +403,9 @@ class DropRTWaitsSpec extends StageSpec(): | val x = Bit <> OUT.REG init 0 | val waitCnt1 = UInt(8) <> VAR.REG init d"8'0" | process: + | def S_0: Step = + | NextStep + | end S_0 | if (x) | x.din := !x | def S_1: Step = @@ -299,7 +413,6 @@ class DropRTWaitsSpec extends StageSpec(): | waitCnt1.din := waitCnt1 + d"8'1" | ThisStep | else NextStep - | end if | end S_1 | waitCnt1.din := d"8'0" | else @@ -321,4 +434,178 @@ class DropRTWaitsSpec extends StageSpec(): |end Foo""".stripMargin ) } + test("basic named steps") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG init 0 + process: + x.din := 1 + def MyStep: Step = + NextStep + end MyStep + 1.cy.wait + end Foo + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG init 0 + | process: + | def S_0: Step = + | NextStep + | end S_0 + | x.din := 1 + | def MyStep: Step = + | NextStep + | end MyStep + | def S_2: Step = + | NextStep + | end S_2 + |end Foo""".stripMargin + ) + } + test("basic named steps with nested steps") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG init 0 + process: + def MyStep: Step = + 1.cy.wait + def Internal: Step = + NextStep + end Internal + 1.cy.wait + NextStep + end MyStep + end Foo + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG init 0 + | process: + | def MyStep: Step = + | def MyStep_0: Step = + | NextStep + | end MyStep_0 + | def MyStep_Internal: Step = + | NextStep + | end MyStep_Internal + | def MyStep_2: Step = + | NextStep + | end MyStep_2 + | NextStep + | end MyStep + |end Foo""".stripMargin + ) + } + test("complex named steps with nested steps, loops, and waits") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG init 0 + process: + def MyStep: Step = + 1.cy.wait + def Internal: Step = + def Deeper: Step = + NextStep + end Deeper + 1.cy.wait + NextStep + end Internal + 1.cy.wait + NextStep + end MyStep + x.din := !x + def MyStepB: Step = + def MyStepB_Internal: Step = + NextStep + end MyStepB_Internal + 1.cy.wait + val MyWait = 1.cy.wait + 1.cy.wait + NextStep + end MyStepB + x.din := !x + val MyWhile = while (x) + x.din := !x + def GoGo: Step = + NextStep + end GoGo + 1.cy.wait + end MyWhile + x.din := !x + end Foo + // run dropRTWaits twice to test the nested step name handling (nothing should change after the first run) + val top = (new Foo).dropRTWaits.dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG init 0 + | process: + | def MyStep: Step = + | def MyStep_0: Step = + | NextStep + | end MyStep_0 + | def MyStep_Internal: Step = + | def MyStep_Internal_Deeper: Step = + | NextStep + | end MyStep_Internal_Deeper + | def MyStep_Internal_1: Step = + | NextStep + | end MyStep_Internal_1 + | NextStep + | end MyStep_Internal + | def MyStep_2: Step = + | NextStep + | end MyStep_2 + | NextStep + | end MyStep + | x.din := !x + | def MyStepB: Step = + | def MyStepB_Internal: Step = + | NextStep + | end MyStepB_Internal + | def MyStepB_1: Step = + | NextStep + | end MyStepB_1 + | def MyStepB_MyWait: Step = + | NextStep + | end MyStepB_MyWait + | def MyStepB_3: Step = + | NextStep + | end MyStepB_3 + | NextStep + | end MyStepB + | x.din := !x + | def MyWhile: Step = + | if (x) + | x.din := !x + | def MyWhile_GoGo: Step = + | NextStep + | end MyWhile_GoGo + | def MyWhile_1: Step = + | NextStep + | end MyWhile_1 + | ThisStep + | else NextStep + | end if + | end MyWhile + | x.din := !x + |end Foo""".stripMargin + ) + } + test("empty loop") { + class Foo extends RTDesign: + process: + while (true) {} + val top = (new Foo).dropRTWaits + assertCodeString( + top, + """|class Foo extends RTDesign: + | process: + | def S_0: Step = + | if (true) ThisStep + | else NextStep + | end S_0 + |end Foo""".stripMargin + ) + } end DropRTWaitsSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/ExplicitCondExprAssignSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ExplicitCondExprAssignSpec.scala index f72bb7255..66b31d69f 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ExplicitCondExprAssignSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ExplicitCondExprAssignSpec.scala @@ -84,7 +84,7 @@ class ExplicitCondExprAssignSpec extends StageSpec(stageCreatesUnrefAnons = true | case sd"16'1" => sd"4'5" | case sd"16'2" => sd"4'3" | end match - | if (x < sd"16'11") z2 := (zz + sd"4'3").resize(16) + | if (x < sd"16'11") z2 := (zz +^ sd"4'3").resize(16) | else z2 := zz.resize(16) | case _ => z2 := z + sd"16'12" | end match diff --git a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala index 8898a807e..8a2e76d42 100644 --- a/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/ExplicitNamedVarsSpec.scala @@ -103,7 +103,7 @@ class ExplicitNamedVarsSpec extends StageSpec: | case sd"16'1" => zz := sd"4'5" | case sd"16'2" => zz := sd"4'3" | end match - | if (x < sd"16'11") z2 := (zz + sd"4'3").resize(16) + | if (x < sd"16'11") z2 := (zz +^ sd"4'3").resize(16) | else z2 := zz.resize(16) | case _ => z2 := z + sd"16'12" | end match @@ -118,10 +118,10 @@ class ExplicitNamedVarsSpec extends StageSpec: val lhs = Bits(8) <> IN val shifted = lhs << 1 val o = Bits(8) <> OUT - o <> (( + o <> ( if (lhs(7)) shifted ^ h"1b" else shifted - ): Bits[8] <> VAL) + ) end xtime val id = (new xtime).explicitNamedVars assertCodeString( @@ -137,4 +137,128 @@ class ExplicitNamedVarsSpec extends StageSpec: ) } + test("Simple named ident") { + class ID extends DFDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val v = x + y := v + end ID + val id = (new ID).explicitNamedVars + assertCodeString( + id, + """|class ID extends DFDesign: + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val v = SInt(16) <> VAR + | v := x + | y := v + |end ID + |""".stripMargin + ) + } + + test("RT process block named values") { + class ID extends RTDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT.REG init 0 + process: + def S0: Step = + val v = x + y.din := v + NextStep + val vr = x + def S1: Step = + y.din := vr + 1 + NextStep + val vrc = (if (x > 5) x else x + 1) + def S2: Step = + y.din := vrc + NextStep + val vm = x + y.din := vm + def S3: Step = + y.din := vm + NextStep + val vrcm = (if (x > 5) x else x + 1) + y.din := vrcm + def S4: Step = + y.din := vrcm + NextStep + end ID + val id = (new ID).explicitNamedVars + assertCodeString( + id, + """|class ID extends RTDesign: + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT.REG init sd"16'0" + | process: + | def S0: Step = + | val v = SInt(16) <> VAR + | v := x + | y.din := v + | NextStep + | end S0 + | val vr = SInt(16) <> VAR.REG + | vr.din := x + | def S1: Step = + | y.din := vr + sd"16'1" + | NextStep + | end S1 + | val vrc = SInt(16) <> VAR.REG + | if (x > sd"16'5") vrc.din := x + | else vrc.din := x + sd"16'1" + | def S2: Step = + | y.din := vrc + | NextStep + | end S2 + | val vm_din = SInt(16) <> VAR + | val vm = SInt(16) <> VAR.REG + | vm_din := x + | vm.din := vm_din + | y.din := vm_din + | def S3: Step = + | y.din := vm + | NextStep + | end S3 + | val vrcm_din = SInt(16) <> VAR + | val vrcm = SInt(16) <> VAR.REG + | if (x > sd"16'5") vrcm_din := x + | else vrcm_din := x + sd"16'1" + | y.din := vrcm_din + | def S4: Step = + | y.din := vrcm + | NextStep + | end S4 + |end ID""".stripMargin + ) + } + test("EDDomain rules") { + class ID extends EDDesign: + val x = SInt(16) <> IN + val y = SInt(16) <> OUT + val py = SInt(16) <> OUT + val v = x + y <> v + process: + val pv = x + py := pv + end ID + val id = (new ID).explicitNamedVars + assertCodeString( + id, + """|class ID extends EDDesign: + | val x = SInt(16) <> IN + | val y = SInt(16) <> OUT + | val py = SInt(16) <> OUT + | val v = SInt(16) <> VAR + | v <> x + | y <> v + | process: + | val pv = SInt(16) <> VAR + | pv := x + | py := pv + |end ID""".stripMargin + ) + } end ExplicitNamedVarsSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/FlattenStepBlocksSpec.scala b/compiler/stages/src/test/scala/StagesSpec/FlattenStepBlocksSpec.scala new file mode 100644 index 000000000..0e342996c --- /dev/null +++ b/compiler/stages/src/test/scala/StagesSpec/FlattenStepBlocksSpec.scala @@ -0,0 +1,524 @@ +package StagesSpec + +import dfhdl.* +import dfhdl.compiler.stages.flattenStepBlocks +// scalafmt: { align.tokens = [{code = "<>"}, {code = "="}, {code = "=>"}, {code = ":="}]} + +class FlattenStepBlocksSpec extends StageSpec(): + + test("single flat step") { + class Foo extends RTDesign: + process: + def S_0: Step = + NextStep + end S_0 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | process: + | def S_0: Step = + | S_0 + | end S_0 + |end Foo""".stripMargin + ) + } + + test("two flat steps") { + class Foo extends RTDesign: + val y = Bit <> OUT.REG init 0 + process: + def S0: Step = + y.din := 0 + NextStep + end S0 + def S1: Step = + y.din := 1 + NextStep + end S1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val y = Bit <> OUT.REG init 0 + | process: + | def S0: Step = + | y.din := 0 + | S1 + | end S0 + | def S1: Step = + | y.din := 1 + | S0 + | end S1 + |end Foo""".stripMargin + ) + } + + test("two flat steps with inter-step statement") { + class Foo extends RTDesign: + val i = Bit <> IN + val x = Bit <> OUT.REG + process: + def S_0: Step = + NextStep + end S_0 + x.din := i + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Bit <> IN + | val x = Bit <> OUT.REG + | process: + | def S_0: Step = + | x.din := i + | S_1 + | end S_0 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + + test("one level of nesting") { + class Foo extends RTDesign: + process: + def MyStep: Step = + def MyStep_0: Step = + NextStep + end MyStep_0 + NextStep + end MyStep + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | process: + | def MyStep: Step = + | MyStep_0 + | end MyStep + | def MyStep_0: Step = + | MyStep + | end MyStep_0 + |end Foo""".stripMargin + ) + } + + test("three flat steps with inter-step statements") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG + val y = Bit <> OUT.REG + process: + def S_0: Step = + NextStep + end S_0 + x.din := 0 + def S_1: Step = + NextStep + end S_1 + y.din := 1 + def S_2: Step = + NextStep + end S_2 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG + | val y = Bit <> OUT.REG + | process: + | def S_0: Step = + | x.din := 0 + | S_1 + | end S_0 + | def S_1: Step = + | y.din := 1 + | S_2 + | end S_1 + | def S_2: Step = + | S_0 + | end S_2 + |end Foo""".stripMargin + ) + } + + test("nested siblings with inter-step statements") { + class Foo extends RTDesign: + val a = Int <> OUT.REG + val b = Int <> OUT.REG + process: + def S_0: Step = + a.din := 1 + def S_0_0: Step = + NextStep + end S_0_0 + b.din := 2 + def S_0_1: Step = + NextStep + end S_0_1 + NextStep + end S_0 + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val a = Int <> OUT.REG + | val b = Int <> OUT.REG + | process: + | def S_0: Step = + | a.din := 1 + | S_0_0 + | end S_0 + | def S_0_0: Step = + | b.din := 2 + | S_0_1 + | end S_0_0 + | def S_0_1: Step = + | S_1 + | end S_0_1 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + + test("two levels of nesting with inner-to-outer statement relocation") { + class Foo extends RTDesign: + val a = Int <> OUT.REG + val b = Int <> OUT.REG + val c = Int <> OUT.REG + process: + def S_0: Step = + def S_0_0: Step = + a.din := 1 + def S_0_0_0: Step = + NextStep + end S_0_0_0 + b.din := 2 + NextStep + end S_0_0 + c.din := 3 + NextStep + end S_0 + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val a = Int <> OUT.REG + | val b = Int <> OUT.REG + | val c = Int <> OUT.REG + | process: + | def S_0: Step = + | S_0_0 + | end S_0 + | def S_0_0: Step = + | a.din := 1 + | S_0_0_0 + | end S_0_0 + | def S_0_0_0: Step = + | b.din := 2 + | c.din := 3 + | S_1 + | end S_0_0_0 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + + test("ThisStep and FirstStep resolution") { + class Foo extends RTDesign: + val i = Bit <> IN + process: + def S_0: Step = + if (i) + ThisStep + else + NextStep + end if + end S_0 + def S_1: Step = + if (i) + FirstStep + else + NextStep + end if + end S_1 + def S_2: Step = + NextStep + end S_2 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Bit <> IN + | process: + | def S_0: Step = + | if (i) S_0 + | else S_1 + | end S_0 + | def S_1: Step = + | if (i) S_0 + | else S_2 + | end S_1 + | def S_2: Step = + | S_0 + | end S_2 + |end Foo""".stripMargin + ) + } + + test("step nested inside conditional branch") { + class Foo extends RTDesign: + val i = Bit <> IN + process: + def S_0: Step = + if (i) + def S_0_0: Step = + NextStep + end S_0_0 + ThisStep + else + NextStep + end if + end S_0 + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Bit <> IN + | process: + | def S_0: Step = + | if (i) S_0_0 + | else S_1 + | end S_0 + | def S_0_0: Step = + | S_0 + | end S_0_0 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + + test("step nested inside conditional branch with inter-step statement") { + class Foo extends RTDesign: + val i = Bit <> IN + val x = Bit <> OUT.REG + process: + def S_0: Step = + if (i) + def S_0_0: Step = + NextStep + end S_0_0 + x.din := 1 + ThisStep + else + NextStep + end if + end S_0 + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Bit <> IN + | val x = Bit <> OUT.REG + | process: + | def S_0: Step = + | if (i) S_0_0 + | else S_1 + | end S_0 + | def S_0_0: Step = + | x.din := 1 + | S_0 + | end S_0_0 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + + test("onEntry and onExit blocks move with their parent step during flattening") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG + process: + def S_0: Step = + def onEntry = + x.din := 1 + end onEntry + def S_0_0: Step = + def onExit = + x.din := 0 + end onExit + NextStep + end S_0_0 + NextStep + end S_0 + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG + | process: + | def S_0: Step = + | def onEntry: Unit = + | x.din := 1 + | end onEntry + | S_0_0 + | end S_0 + | def S_0_0: Step = + | def onExit: Unit = + | x.din := 0 + | end onExit + | S_1 + | end S_0_0 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + + test( + "multiple inter-step statements from nested and conditional scopes collected before single NextStep" + ) { + class Foo extends RTDesign: + val a = Int <> OUT.REG + val b = Int <> OUT.REG + val c = Int <> OUT.REG + val i = Bit <> IN + process: + def S_0: Step = + def S_0_0: Step = + if (i) + a.din := 1 + else + a.din := 0 + end if + NextStep + end S_0_0 + b.din := 2 + NextStep + end S_0 + c.din := 3 + def S_1: Step = + NextStep + end S_1 + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val a = Int <> OUT.REG + | val b = Int <> OUT.REG + | val c = Int <> OUT.REG + | val i = Bit <> IN + | process: + | def S_0: Step = + | S_0_0 + | end S_0 + | def S_0_0: Step = + | if (i) a.din := 1 + | else a.din := 0 + | b.din := 2 + | c.din := 3 + | S_1 + | end S_0_0 + | def S_1: Step = + | S_0 + | end S_1 + |end Foo""".stripMargin + ) + } + test("multiple steps nested inside the same conditional branch with inter-step statements") { + class Foo extends RTDesign: + val i = Int <> VAR.REG + process: + def S_0: Step = + NextStep + end S_0 + i.din := 0 + def S_1: Step = + if (i < 3) + println(s"Hello") + def S_1_0: Step = + NextStep + end S_1_0 + println(s"World") + def S_1_1: Step = + NextStep + end S_1_1 + println(s"!") + i.din := i + 1 + ThisStep + else NextStep + end if + end S_1 + finish() + end Foo + val top = (new Foo).flattenStepBlocks + assertCodeString( + top, + """|class Foo extends RTDesign: + | val i = Int <> VAR.REG + | process: + | def S_0: Step = + | i.din := 0 + | S_1 + | end S_0 + | def S_1: Step = + | if (i < 3) + | println(s"Hello") + | S_1_0 + | else + | finish() + | S_0 + | end S_1 + | def S_1_0: Step = + | println(s"World") + | S_1_1 + | end S_1_0 + | def S_1_1: Step = + | println(s"!") + | i.din := i + 1 + | S_1 + | end S_1_1 + |end Foo""".stripMargin + ) + } + +end FlattenStepBlocksSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala index 7dc350a90..a38402cdf 100644 --- a/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/GlobalizePortVectorParamsSpec.scala @@ -4,7 +4,7 @@ import dfhdl.* import dfhdl.compiler.stages.globalizePortVectorParams // scalafmt: { align.tokens = [{code = "<>"}, {code = "="}, {code = "=>"}, {code = ":="}]} -class GlobalizePortVectorParams extends StageSpec(stageCreatesUnrefAnons = true): +class GlobalizePortVectorParamsSpec extends StageSpec(stageCreatesUnrefAnons = true): given options.CompilerOptions.Backend = backends.vhdl.v93 test("Various vector params are kept"): val width: Int <> CONST = 8 @@ -46,9 +46,13 @@ class GlobalizePortVectorParams extends StageSpec(stageCreatesUnrefAnons = true) ) test("Various vector params are globalized, only ports are affected"): class Foo( - val width: Int <> CONST = 8, - val length: Int <> CONST = 10 + val keepThisParam: Int <> CONST = 4, + val width: Int <> CONST = 8, + val length: Int <> CONST = 10 ) extends RTDesign: + val x0 = Bits(keepThisParam) <> IN + val y0 = Bits(keepThisParam) <> OUT + y0 <> x0 val x1 = Bits(width) X length <> IN val y1 = Bits(width) X length <> OUT val v1 = Bits(width) X length <> VAR @@ -76,7 +80,10 @@ class GlobalizePortVectorParams extends StageSpec(stageCreatesUnrefAnons = true) """|val Foo_length: Int <> CONST = 10 |val Foo_width: Int <> CONST = 8 | - |class Foo extends RTDesign: + |class Foo(val keepThisParam: Int <> CONST = 4) extends RTDesign: + | val x0 = Bits(keepThisParam) <> IN + | val y0 = Bits(keepThisParam) <> OUT + | y0 <> x0 | val x1 = Bits(Foo_width) X Foo_length <> IN | val y1 = Bits(Foo_width) X Foo_length <> OUT | val v1 = Bits(Foo_width) X Foo_length <> VAR @@ -312,4 +319,52 @@ class GlobalizePortVectorParams extends StageSpec(stageCreatesUnrefAnons = true) | y3 <> id3.y |end IDTop""".stripMargin ) -end GlobalizePortVectorParams + + test("Duplicate designs with nested design instances"): + class ID extends EDDesign: + val x = Bits(8) <> IN + val y = Bits(8) <> OUT + y <> x + end ID + + class Internal(val param: Bits[8] <> CONST) extends EDDesign: + val x = Bits(8) <> IN + val y = Bits(8) <> OUT + val id = ID() + id.x <> x + y <> id.y + end Internal + + class Foo extends EDDesign: + val state = Bits(8) <> IN + val o00 = Internal(param = h"02") + val o01 = Internal(param = h"01") + o00.x <> state + o01.x <> state + val top = (new Foo).globalizePortVectorParams + assertCodeString( + top, + """|class ID extends EDDesign: + | val x = Bits(8) <> IN + | val y = Bits(8) <> OUT + | y <> x + |end ID + | + |class Internal(val param: Bits[8] <> CONST) extends EDDesign: + | val x = Bits(8) <> IN + | val y = Bits(8) <> OUT + | val id = ID() + | id.x <> x + | y <> id.y + |end Internal + | + |class Foo extends EDDesign: + | val state = Bits(8) <> IN + | val o00 = Internal(param = h"02") + | val o01 = Internal(param = h"01") + | o00.x <> state + | o01.x <> state + |end Foo + |""".stripMargin + ) +end GlobalizePortVectorParamsSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/LocalToDesignParamsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/LocalToDesignParamsSpec.scala index cff228795..8f9657e2f 100644 --- a/compiler/stages/src/test/scala/StagesSpec/LocalToDesignParamsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/LocalToDesignParamsSpec.scala @@ -205,10 +205,7 @@ class LocalToDesignParamsSpec extends StageSpec: |class PrimeDiv5 extends RTDesign: | val primeDiv5_DIVIDEND_WIDTH: Int <> CONST = 5 + 1 | val dividend = UInt(6) <> VAR.REG init d"6'0" - | val primeDiv5 = PrimeDiv( - | primeDivisor = 5, - | DIVIDEND_WIDTH = primeDiv5_DIVIDEND_WIDTH - | ) + | val primeDiv5 = PrimeDiv(primeDivisor = 5) | primeDiv5.dividend <> dividend |end PrimeDiv5""".stripMargin ) diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala index c7230c505..beeebd8dd 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintCodeStringSpec.scala @@ -201,6 +201,7 @@ class PrintCodeStringSpec extends StageSpec: test("RTDesign with class extension and parameters") { val gp: Bit <> CONST = 1 + val gp2 = gp val i: SInt[16] <> CONST = 0 val i2 = i + 5 class ID(dp: Bit <> CONST) extends RTDesign: @@ -210,19 +211,20 @@ class PrintCodeStringSpec extends StageSpec: val flag = Bit <> IN init dp || gp y := x.reg.reg(2, init = c - i) - x end ID - class IDExt(dpNew: Bit <> CONST) extends ID(gp && dpNew): + class IDExt(dpNew: Bit <> CONST) extends ID(gp2 && dpNew): val z = Bit <> OUT z := dpNew val id = (new IDExt(gp)).getCodeString assertNoDiff( id, - """|val gp: Bit <> CONST = 1 - |val i: SInt[16] <> CONST = sd"16'0" + """|val i: SInt[16] <> CONST = sd"16'0" |val i2: SInt[16] <> CONST = i + sd"16'5" + |val gp: Bit <> CONST = 1 + |val gp2: Bit <> CONST = gp | |class IDExt( - | val dp: Bit <> CONST = gp && gp, + | val dp: Bit <> CONST = gp2 && gp, | val dpNew: Bit <> CONST = gp |) extends RTDesign: | val c: SInt[16] <> CONST = sd"16'3" @@ -992,7 +994,8 @@ class PrintCodeStringSpec extends StageSpec: 10.ms.wait if (i) S_2 else S_1 def S_2: Step = - def onEntry = + def fallThrough = !i + def onEntry = x := 0 def onExit = x := 1 @@ -1015,6 +1018,9 @@ class PrintCodeStringSpec extends StageSpec: | else S_1 | end S_1 | def S_2: Step = + | def fallThrough: Bit <> VAL = + | !i + | end fallThrough | def onEntry: Unit = | x := 0 | end onEntry @@ -1171,6 +1177,57 @@ class PrintCodeStringSpec extends StageSpec: |end Foo""".stripMargin ) } + test("for/while loop printing with FALL_THROUGH") { + class Foo extends RTDesign: + val matrix = Bits(10) X 8 X 8 <> OUT.REG + process: + for ( + i <- 0 until 8; + if i % 2 == 0; + j <- 0 until 8; + if j % 2 == 0; + k <- 0 until 10 + if k % 2 == 0 + ) + FALL_THROUGH + matrix(i)(j)(k).din := 1 + val ii = UInt.until(8) <> VAR init 0 + while (ii != 7) + FALL_THROUGH + matrix(ii)(0)(0).din := 0 + ii := ii + 1 + 10.sec.wait + end Foo + val top = (new Foo).getCodeString + assertNoDiff( + top, + """|class Foo extends RTDesign: + | val matrix = Bits(10) X 8 X 8 <> OUT.REG + | process: + | for (i <- 0 until 8) + | FALL_THROUGH + | if ((i % 2) == 0) + | for (j <- 0 until 8) + | FALL_THROUGH + | if ((j % 2) == 0) + | for (k <- 0 until 10) + | FALL_THROUGH + | if ((k % 2) == 0) matrix(i)(j)(k).din := 1 + | end for + | end if + | end for + | end if + | end for + | val ii = UInt(3) <> VAR init d"3'0" + | while (ii != d"3'7") + | FALL_THROUGH + | matrix(ii.toInt)(0)(0).din := 0 + | ii := ii + d"3'1" + | end while + | 10.sec.wait + |end Foo""".stripMargin + ) + } test("while loop printing") { class Foo extends EDDesign: val x = Bit <> OUT @@ -1606,4 +1663,74 @@ class PrintCodeStringSpec extends StageSpec: |end Foo""".stripMargin ) } + + test("named rt-process loops") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG init 0 + process: + val MyWhile = while (x) + def GoGo: Step = + NextStep + end GoGo + x.din := !x + end MyWhile + val MyFor = for (i <- 0 until 10) + def GoGo: Step = + NextStep + end GoGo + x.din := !x + end MyFor + end Foo + val top = (new Foo).getCodeString + assertNoDiff( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG init 0 + | process: + | val MyWhile = while (x) + | def GoGo: Step = + | NextStep + | end GoGo + | x.din := !x + | end MyWhile + | val MyFor = for (i <- 0 until 10) + | def GoGo: Step = + | NextStep + | end GoGo + | x.din := !x + | end MyFor + |end Foo""".stripMargin + ) + } + + test("parameterized selection") { + class Foo extends DFDesign: + val LEN: Int <> CONST = 8 + val v = Bits(LEN) <> VAR + val o = v(LEN - 2, 0) + val top = (new Foo).getCodeString + assertNoDiff( + top, + """|class Foo extends DFDesign: + | val LEN: Int <> CONST = 8 + | val v = Bits(LEN) <> VAR + | val o = v(LEN - 2, 0) + |end Foo""".stripMargin + ) + } + + test("cascade aliasing regression") { + class Foo() extends DFDesign: + val x = UInt(3) <> IN + val y = x.resize(16).bits.sint + end Foo + val top = (new Foo).getCodeString + assertNoDiff( + top, + """|class Foo extends DFDesign: + | val x = UInt(3) <> IN + | val y = x.resize(16).bits.sint + |end Foo""".stripMargin + ) + } end PrintCodeStringSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala index ac5ca6080..89e21aec3 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala @@ -39,7 +39,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.ID_pkg.all; | |entity ID is |port ( @@ -64,7 +63,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.IDTop_pkg.all; | |entity ID is |port ( @@ -82,7 +80,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.IDTop_pkg.all; | |entity IDTop is |port ( @@ -133,7 +130,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.IDTop_pkg.all; | |entity ID is |generic ( @@ -154,7 +150,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.IDTop_pkg.all; | |entity IDTop is |generic ( @@ -223,7 +218,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Top_pkg.all; | |entity Top is |port ( @@ -290,7 +284,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.numeric_std.all; |use ieee.math_real.all; |use work.dfhdl_pkg.all; - |use work.Top_pkg.all; | |entity Top is |end Top; @@ -360,7 +353,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Blinker_pkg.all; | |entity Blinker is |generic ( @@ -414,7 +406,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Example_pkg.all; | |entity Example is |port ( @@ -611,7 +602,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.IDTop_pkg.all; | |entity IDTop is |port ( @@ -657,7 +647,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.SelOp_pkg.all; | |entity SelOp is |port ( @@ -694,7 +683,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Empty_pkg.all; | |entity Empty is |end Empty; @@ -719,7 +707,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.HighZ_pkg.all; | |entity HighZ is |port ( @@ -756,7 +743,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -795,7 +781,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -907,7 +892,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -960,7 +944,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -1077,7 +1060,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -1122,7 +1104,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -1188,7 +1169,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |generic ( @@ -1231,7 +1211,7 @@ class PrintVHDLCodeSpec extends StageSpec: | println("These are the values: " & to_string(param3) & ", " & to_string(param4) & ", " & to_string(param5) & ", " & to_string(param6) & ", " & to_string(param7) & ", " & to_string(param8) & ", " & to_string(param9) & ", " & t_enum_MyEnum'image(param10) & ""); | report | "Debug at Foo" & LF & - | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1177:9" & LF & + | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1158:9" & LF & | "param3 = " & to_string(param3) & LF & | "param4 = " & to_string(param4) & LF & | "param5 = " & to_string(param5) & LF & @@ -1250,7 +1230,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |generic ( @@ -1293,7 +1272,7 @@ class PrintVHDLCodeSpec extends StageSpec: | println("These are the values: " & to_string(param3) & ", " & to_string(param4) & ", " & to_string(param5) & ", " & to_string(param6) & ", " & to_string(param7) & ", " & to_string(param8) & ", " & to_string(param9) & ", " & t_enum_MyEnum'image(param10) & ""); | report | "Debug at Foo" & LF & - | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1177:9" & LF & + | "compiler/stages/src/test/scala/StagesSpec/PrintVHDLCodeSpec.scala:1158:9" & LF & | "param3 = " & to_string(param3) & LF & | "param4 = " & to_string(param4) & LF & | "param5 = " & to_string(param5) & LF & @@ -1325,7 +1304,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |generic ( @@ -1374,7 +1352,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -1475,7 +1452,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -1517,7 +1493,6 @@ class PrintVHDLCodeSpec extends StageSpec: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.Foo_pkg.all; | |entity Foo is |port ( @@ -1532,4 +1507,74 @@ class PrintVHDLCodeSpec extends StageSpec: |end Foo_arch;""".stripMargin ) } + test("globalDefsFileName override") { + given options.PrinterOptions.GlobalDefsFileName = "otherGlobal" + enum MyEnum extends Encoded: + case A, B + class Foo extends RTDesign: + val x = MyEnum <> IN + val y = MyEnum <> OUT + y := x + val top = (new Foo).getCompiledCodeString + assertNoDiff( + top, + """|type t_enum_MyEnum is ( + | MyEnum_A, MyEnum_B + |); + | + |library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + |use work.otherGlobal.all; + | + |entity Foo is + |port ( + | x : in t_enum_MyEnum; + | y : out t_enum_MyEnum + |); + |end Foo; + | + |architecture Foo_arch of Foo is + |begin + | y <= x; + |end Foo_arch; + |""".stripMargin + ) + } + test("globalDefsFileName override with suffix") { + given options.PrinterOptions.GlobalDefsFileName = "otherGlobal.vhd" + enum MyEnum extends Encoded: + case A, B + class Foo extends RTDesign: + val x = MyEnum <> IN + val y = MyEnum <> OUT + y := x + val top = (new Foo).getCompiledCodeString + assertNoDiff( + top, + """|type t_enum_MyEnum is ( + | MyEnum_A, MyEnum_B + |); + | + |library ieee; + |use ieee.std_logic_1164.all; + |use ieee.numeric_std.all; + |use work.dfhdl_pkg.all; + |use work.otherGlobal.all; + | + |entity Foo is + |port ( + | x : in t_enum_MyEnum; + | y : out t_enum_MyEnum + |); + |end Foo; + | + |architecture Foo_arch of Foo is + |begin + | y <= x; + |end Foo_arch; + |""".stripMargin + ) + } end PrintVHDLCodeSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala index 7b3efa223..4cf1700c3 100644 --- a/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/PrintVerilogCodeSpec.scala @@ -41,7 +41,6 @@ class PrintVerilogCodeSpec extends StageSpec: id, """|`default_nettype none |`timescale 1ns/1ps - |`include "ID_defs.svh" | |module ID( | input wire logic signed [15:0] x, @@ -62,7 +61,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.svh" | |module ID( | input wire logic signed [15:0] x, @@ -76,7 +74,6 @@ class PrintVerilogCodeSpec extends StageSpec: | |`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.svh" | |module IDTop( | input wire logic signed [15:0] x, @@ -124,7 +121,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.svh" | |module ID#(parameter int width = 7)( | input wire logic signed [width - 1:0] x, @@ -136,7 +132,6 @@ class PrintVerilogCodeSpec extends StageSpec: | |`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.svh" | |module IDTop#(parameter int width = 16)( | input wire logic signed [width - 1:0] x, @@ -187,14 +182,12 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.vh" | |module ID( | x, | y |); | `include "dfhdl_defs.vh" - | `include "IDTop_defs.vh" | parameter integer width = 7; | input wire [width - 1:0] x; | output wire [width - 1:0] y; @@ -203,14 +196,12 @@ class PrintVerilogCodeSpec extends StageSpec: | |`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.vh" | |module IDTop( | x, | y |); | `include "dfhdl_defs.vh" - | `include "IDTop_defs.vh" | parameter integer width = 16; | input wire [width - 1:0] x; | output wire [width - 1:0] y; @@ -265,6 +256,34 @@ class PrintVerilogCodeSpec extends StageSpec: ) } + test("Boolean logical operators") { + class BoolLogic extends RTDesign: + val a = Boolean <> IN + val b = Boolean <> IN + val y1 = Boolean <> OUT + val y2 = Boolean <> OUT + y1 := a || b + y2 := a && b + val top = (new BoolLogic).getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module BoolLogic( + | input wire logic a, + | input wire logic b, + | output logic y1, + | output logic y2 + |); + | `include "dfhdl_defs.svh" + | assign y1 = a || b; + | assign y2 = a && b; + |endmodule + |""".stripMargin + ) + } + test("process block") { given options.PrinterOptions.Align = true class Top extends EDDesign: @@ -295,7 +314,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Top_defs.svh" | |module Top( | input wire logic clk, @@ -353,7 +371,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Top_defs.svh" | |module Top; | `include "dfhdl_defs.svh" @@ -415,7 +432,6 @@ class PrintVerilogCodeSpec extends StageSpec: """|/* HasDocs has docs */ |`default_nettype none |`timescale 1ns/1ps - |`include "HasDocs_defs.svh" | |module HasDocs( | /* My in */ @@ -444,7 +460,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Counter_defs.svh" | |module Counter#(parameter int width = 8)( | input wire logic clk, @@ -482,7 +497,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Test_defs.svh" | |module Test#(parameter int width = 10)( | output logic [width - 1:0] x, @@ -515,7 +529,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Counter_defs.svh" | |module Counter#(parameter int width = 8)( | input wire logic clk, @@ -557,7 +570,6 @@ class PrintVerilogCodeSpec extends StageSpec: """|/* This is a led blinker */ |`default_nettype none |`timescale 1ns/1ps - |`include "Blinker_defs.svh" | |module Blinker#( | parameter int CLK_FREQ_KHz = 50000, @@ -600,7 +612,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "IDTop_defs.svh" | |module IDTop( | input wire logic clk, @@ -637,7 +648,6 @@ class PrintVerilogCodeSpec extends StageSpec: id, """|`default_nettype none |`timescale 1ns/1ps - |`include "SelOp_defs.svh" | |module SelOp( | input wire logic c, @@ -668,7 +678,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Empty_defs.svh" | |module Empty; | `include "dfhdl_defs.svh" @@ -688,7 +697,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "HighZ_defs.svh" | |module HighZ( | input wire logic [7:0] x, @@ -718,7 +726,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | input wire logic [15:0] x, @@ -751,19 +758,17 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.vh" | |module Foo( | x, | y |); | `include "dfhdl_defs.vh" - | `include "Foo_defs.vh" | input wire [15:0] x; | output reg [15:0] y; | always @(x) | begin - | if ((x[15:8] == 8'h12) | (x[15:4] == 12'h345)) y = 16'h22??; + | if ((x[15:8] == 8'h12) || (x[15:4] == 12'h345)) y = 16'h22??; | else y = 16'hffff; | end |endmodule @@ -864,7 +869,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | output logic x, @@ -915,7 +919,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | output logic [9:0] matrix [0:7] [0:7] @@ -979,13 +982,11 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.vh" | |module Foo( | output reg [639:0] matrix |); | `include "dfhdl_defs.vh" - | `include "Foo_defs.vh" | | always | begin @@ -1036,7 +1037,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | output logic x, @@ -1096,12 +1096,11 @@ class PrintVerilogCodeSpec extends StageSpec: sv2005.csTop, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo#(parameter string param = "Hello\n..\"World\"!"); | `include "dfhdl_defs.svh" | localparam int param3 = 42; - | typedef enum { + | typedef enum logic [1:0] { | MyEnum_A = 0, | MyEnum_B = 1, | MyEnum_C = 2 @@ -1152,11 +1151,9 @@ class PrintVerilogCodeSpec extends StageSpec: v95.csTop, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.vh" | |module Foo; | `include "dfhdl_defs.vh" - | `include "Foo_defs.vh" | parameter param = "Hello\n..\"World\"!"; | parameter integer param3 = 42; | `define MyEnum_A 0 @@ -1237,7 +1234,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo#(parameter int PORT_WIDTH = 5)( | input wire logic clk, @@ -1277,11 +1273,9 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.vh" | |module Foo; | `include "dfhdl_defs.vh" - | `include "Foo_defs.vh" | parameter integer PORT_WIDTH = 8; | parameter integer PORT_DEPTH = 4; | parameter [(PORT_WIDTH * PORT_DEPTH) - 1:0] initArg = {8'h01, 8'h02, 8'h03, 8'h04}; @@ -1427,7 +1421,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | input wire logic x, @@ -1455,7 +1448,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | input wire logic clk, @@ -1484,7 +1476,6 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.svh" | |module Foo( | input wire logic signed [7:0] x, @@ -1508,18 +1499,121 @@ class PrintVerilogCodeSpec extends StageSpec: top, """|`default_nettype none |`timescale 1ns/1ps - |`include "Foo_defs.vh" | |module Foo( | x, | y |); | `include "dfhdl_defs.vh" - | `include "Foo_defs.vh" | input wire [7:0] x; | output wire [7:0] y; | assign y = `ABS(x); |endmodule""".stripMargin ) } + test("globalDefsFileName override") { + given options.PrinterOptions.GlobalDefsFileName = "otherGlobal" + enum MyEnum extends Encoded: + case A, B + class Foo extends RTDesign: + val x = MyEnum <> IN + val y = MyEnum <> OUT + y := x + val top = (new Foo).getCompiledCodeString + assertNoDiff( + top, + """|typedef enum logic [0:0] { + | MyEnum_A = 0, + | MyEnum_B = 1 + |} t_enum_MyEnum; + | + |`default_nettype none + |`timescale 1ns/1ps + |`include "otherGlobal.svh" + | + |module Foo( + | input wire t_enum_MyEnum x, + | output t_enum_MyEnum y + |); + | `include "dfhdl_defs.svh" + | assign y = x; + |endmodule + |""".stripMargin + ) + } + test("globalDefsFileName override with suffix") { + given options.PrinterOptions.GlobalDefsFileName = "otherGlobal.svh" + enum MyEnum extends Encoded: + case A, B + class Foo extends RTDesign: + val x = MyEnum <> IN + val y = MyEnum <> OUT + y := x + val top = (new Foo).getCompiledCodeString + assertNoDiff( + top, + """|typedef enum logic [0:0] { + | MyEnum_A = 0, + | MyEnum_B = 1 + |} t_enum_MyEnum; + | + |`default_nettype none + |`timescale 1ns/1ps + |`include "otherGlobal.svh" + | + |module Foo( + | input wire t_enum_MyEnum x, + | output t_enum_MyEnum y + |); + | `include "dfhdl_defs.svh" + | assign y = x; + |endmodule + |""".stripMargin + ) + } + test("vector to bits conversion") { + class Foo extends EDDesign: + val i1 = Bit X 8 <> IN + val o1 = Bits(8) <> OUT + val i2 = Bits(8) <> IN + val o2 = Bit X 8 <> OUT + o1 <> i1.bits + o2 <> i2.as(Bit X 8) + end Foo + val top = (new Foo).getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module Foo( + | input wire logic i1 [0:7], + | output logic [7:0] o1, + | input wire logic [7:0] i2, + | output logic o2 [0:7] + |); + | `include "dfhdl_defs.svh" + | assign o1 = {i1[0], i1[1], i1[2], i1[3], i1[4], i1[5], i1[6], i1[7]}; + | assign o2 = '{i2[7], i2[6], i2[5], i2[4], i2[3], i2[2], i2[1], i2[0]}; + |endmodule""".stripMargin + ) + } + test("signed literal with parametric width") { + class Foo( + val WIDTH: Int <> CONST = 8 + ) extends EDDesign: + val arg = sd"${WIDTH}'2" + end Foo + val top = (new Foo).getCompiledCodeString + assertNoDiff( + top, + """|`default_nettype none + |`timescale 1ns/1ps + | + |module Foo#(parameter int WIDTH = 8); + | `include "dfhdl_defs.svh" + | localparam logic signed [WIDTH - 1:0] arg = WIDTH'(3'sd2); + |endmodule""".stripMargin + ) + } end PrintVerilogCodeSpec diff --git a/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala b/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala index 32107d209..323a601e9 100644 --- a/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala +++ b/compiler/stages/src/test/scala/StagesSpec/SimplifyRTOpsSpec.scala @@ -62,10 +62,8 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): x.din := i.rising x.din := i.falling waitUntil(i) - waitUntil(i.falling) + val MyWait = waitUntil(i.falling) waitUntil(i.rising) - val temp = i.rising - waitUntil(temp) x.din := 0 end Foo val top = (new Foo).simplifyRTOps @@ -80,13 +78,10 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): | x.din := i.reg(1, init = 0) && (!i) | while (!i) | end while - | while ((!i.reg(1, init = 0)) || i) - | end while + | val MyWait = while ((!i.reg(1, init = 0)) || i) + | end MyWait | while (i.reg(1, init = 1) || (!i)) | end while - | val temp = (!i.reg(1, init = 1)) && i - | while (!temp) - | end while | x.din := 0 |end Foo""".stripMargin ) @@ -101,7 +96,7 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): x.din := 1 waitWhile(i) x.din := 0 - waitWhile(j) + val MyWait = waitWhile(j) x.din := 1 end Foo val top = (new Foo).simplifyRTOps @@ -116,8 +111,8 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): | while (i) | end while | x.din := 0 - | while (j) - | end while + | val MyWait = while (j) + | end MyWait | x.din := 1 |end Foo""".stripMargin ) @@ -131,7 +126,7 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): x.din := 1 50000000.cy.wait x.din := 0 - waitParam.cy.wait + val MyWait = waitParam.cy.wait 1.cy.wait end Foo val top = (new Foo).simplifyRTOps @@ -142,17 +137,154 @@ class SimplifyRTOpsSpec extends StageSpec(stageCreatesUnrefAnons = true): | val waitParam: UInt[26] <> CONST = d"26'50000000" | process: | x.din := 1 - | val waitCnt = UInt(26) <> VAR.REG init d"26'0" + | val waitCnt = UInt(26) <> VAR.REG + | waitCnt.din := d"26'0" | while (waitCnt != d"26'49999999") | waitCnt.din := waitCnt + d"26'1" | end while | x.din := 0 - | val waitCnt = UInt(26) <> VAR.REG init d"26'0" - | while (waitCnt != (waitParam - d"26'1")) - | waitCnt.din := waitCnt + d"26'1" - | end while + | val MyWait_waitCnt = UInt(26) <> VAR.REG + | MyWait_waitCnt.din := d"26'0" + | val MyWait = while (MyWait_waitCnt != (waitParam - d"26'1")) + | MyWait_waitCnt.din := MyWait_waitCnt + d"26'1" + | end MyWait | 1.cy.wait |end Foo""".stripMargin ) } + test("RT for loop with until is converted to while loop") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG + process: + x.din := 1 + for (i <- 0 until 4) + x.din := 0 + x.din := 1 + end Foo + val top = (new Foo).simplifyRTOps + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG + | process: + | x.din := 1 + | val i = Int <> VAR.REG + | i.din := 0 + | while (i < 4) + | x.din := 0 + | i.din := i + 1 + | end while + | x.din := 1 + |end Foo""".stripMargin + ) + } + + test("RT for loop with to is converted to while loop with <= guard") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG + process: + val MyFor = for (i <- 0 to 3) + x.din := 0 + end Foo + val top = (new Foo).simplifyRTOps + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG + | process: + | val MyFor_i = Int <> VAR.REG + | MyFor_i.din := 0 + | val MyFor = while (MyFor_i <= 3) + | x.din := 0 + | MyFor_i.din := MyFor_i + 1 + | end MyFor + |end Foo""".stripMargin + ) + } + + test("RT for loop with explicit step is converted to while loop with step increment") { + class Foo extends RTDesign: + val x = Bit <> OUT.REG + process: + for (i <- 0 until 8 by 2) + x.din := 0 + end Foo + val top = (new Foo).simplifyRTOps + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bit <> OUT.REG + | process: + | val i = Int <> VAR.REG + | i.din := 0 + | while (i < 8) + | x.din := 0 + | i.din := i + 2 + | end while + |end Foo""".stripMargin + ) + } + + test("RT combinational for loop is untouched") { + class Foo extends RTDesign: + val x = Bits(4) <> OUT.REG + process: + x.din := all(0) + for (i <- 0 until 4) + COMB_LOOP + x(i).din := 1 + end Foo + val top = (new Foo).simplifyRTOps + assertCodeString( + top, + """|class Foo extends RTDesign: + | val x = Bits(4) <> OUT.REG + | process: + | x.din := h"0" + | for (i <- 0 until 4) + | COMB_LOOP + | x(i).din := 1 + | end for + |end Foo""".stripMargin + ) + } + + test("RT for loop outside a process is untouched") { + class Foo(val WIDTH: Int <> CONST = 4) extends RTDesign: + val r = Bits(WIDTH) <> OUT.REG init all(0) + for (i <- 0 until WIDTH) + r(i).din := 1 + end Foo + val top = (new Foo).simplifyRTOps + assertCodeString( + top, + """|class Foo(val WIDTH: Int <> CONST = 4) extends RTDesign: + | val r = Bits(WIDTH) <> OUT.REG init b"0".repeat(WIDTH) + | for (i <- 0 until WIDTH) + | r(i).din := 1 + | end for + |end Foo""".stripMargin + ) + } + + test("ED domain for loop is untouched") { + class Foo extends EDDesign: + val x = Bit <> OUT + process: + for (i <- 0 until 4) + x :== 0 + end Foo + val top = (new Foo).simplifyRTOps + assertCodeString( + top, + """|class Foo extends EDDesign: + | val x = Bit <> OUT + | process: + | for (i <- 0 until 4) + | x :== 0 + | end for + |end Foo""".stripMargin + ) + } + end SimplifyRTOpsSpec diff --git a/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala b/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala index 94ea24d27..d17e5a91d 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/MetaDesign.scala @@ -6,6 +6,32 @@ import scala.annotation.unchecked.uncheckedVariance import dfhdl.compiler.ir.DFDesignBlock type MetaDesignAny = MetaDesign[DomainType] + +/** A base class for synthesising new IR members inside a compiler stage and injecting them into the + * DB at a precise position relative to an anchor member. + * + * Extend this class anonymously inside a stage's `transform` method to write ordinary DFHDL DSL + * code (ports, vars, assignments, etc.). The members created in the body are extracted and + * inserted into the DB when `dsn.patch` is applied; the MetaDesign block itself is discarded. + * + * ===Constructing raw IR members inside the body=== + * + * To build an IR member directly (e.g. `ir.Goto`) rather than through the DSL, add + * `import dfhdl.core.*` at the top of the anonymous body. This brings `refTW` and `addMember` into + * scope. Use `dfc.ownerOrEmptyRef` to obtain the owner ref — do not use `dfc.owner.ref` as that + * extension method may conflict with other imports: + * {{{ + * val dsn = new MetaDesign(anchor, Patch.Add.Config.Before): + * import dfhdl.core.* + * ir.Goto(existingStep.refTW[ir.Goto], dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags).addMember + * }}} + * + * ===Ownership=== + * + * For `Before` / `After` / `ReplaceWith*` configs, newly created members are placed as siblings of + * the anchor (same owner). For `InsideFirst` / `InsideLast`, members are placed as direct children + * of the anchor (which must be a `DFOwner`). + */ abstract class MetaDesign[+D <: DomainType]( positionMember: ir.DFMember, addCfg: AddCfg, @@ -32,7 +58,9 @@ abstract class MetaDesign[+D <: DomainType]( final override private[dfhdl] def initOwner: Design.Block = dfc.mutableDB.addMember(injectedOwner) injectedOwner.getThisOrOwnerDesign.asFE - final override protected def __dfc: DFC = DFC.emptyNoEO.copy(refGen = refGen) + // default context position is set according to the positionMember + final override protected def __dfc: DFC = + DFC.emptyNoEO.copy(refGen = refGen, position = positionMember.meta.position) injectedOwner match case design: ir.DFDesignBlock => // do nothing @@ -47,10 +75,7 @@ abstract class MetaDesign[+D <: DomainType]( final lazy val __domainType: ir.DomainType = ir.DomainType.DF final def plantMember[T <: ir.DFMember](member: T): T = - if (globalInjection) - dfc.mutableDB.plantMember(ir.DFMember.Empty, member) - else - dfc.mutableDB.plantMember(dfc.owner.asIR, member) + dfc.mutableDB.plantMember(dfc.owner.asIR, member) final def plantMembers(baseOwner: ir.DFOwner, members: Iterable[ir.DFMember]): Unit = members.foreach { m => val owner = m.getOwner @@ -62,7 +87,10 @@ abstract class MetaDesign[+D <: DomainType]( else owner dfc.mutableDB.plantMember(updatedOwner, m, _ => cond) } - final def plantClonedMembers(baseOwner: ir.DFOwner, members: List[ir.DFMember]): Unit = + final def plantClonedMembers( + baseOwner: ir.DFOwner, + members: List[ir.DFMember] + ): Map[ir.DFMember, ir.DFMember] = val clonedMemberMap = members.map { m => m -> m.copyWithNewRefs }.toMap members.foreach { m => val cloned = clonedMemberMap(m) @@ -74,6 +102,8 @@ abstract class MetaDesign[+D <: DomainType]( dfc.mutableDB.newRefFor(clonedRef, clonedMemberMap.getOrElse(refMember, refMember)) } } + clonedMemberMap + end plantClonedMembers final def applyBlock(owner: ir.DFOwner)(block: => Unit): Unit = dfc.mutableDB.OwnershipContext.enter(owner) block diff --git a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala index 272cb4917..48deb1c06 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/Patch.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/Patch.scala @@ -41,7 +41,7 @@ object Patch: def apply(refs: Set[DFRefAny])(using MemberGetSet): Set[DFRefAny] = refs -- rf(refs) // All references are replaced - object All extends RefFilter: + case object All extends RefFilter: def apply(refs: Set[DFRefAny])(using MemberGetSet): Set[DFRefAny] = refs // Only references from outside the given owner are replaced final case class Outside(block: DFOwner) extends RefFilter: @@ -51,6 +51,10 @@ object Patch: final case class Inside(block: DFOwner) extends RefFilter: def apply(refs: Set[DFRefAny])(using MemberGetSet): Set[DFRefAny] = refs.collect { case r: DFRef.TwoWayAny if r.originMember.isInsideOwner(block) => r } + // Only references to the given members are replaced + final case class OfMembers(members: Set[DFMember]) extends RefFilter: + def apply(refs: Set[DFRefAny])(using MemberGetSet): Set[DFRefAny] = + refs.collect { case r: DFRef.TwoWayAny if members.contains(r.originMember) => r } end RefFilter end Replace final case class Add private[patching] (db: DB, config: Add.Config) extends Patch: @@ -101,8 +105,36 @@ object Patch: case object Via extends Config end Config end Add - // movedMembers: members to move - // origOwner: the original owner of the top members + + /** Moves `movedMembers` to a new position in the flat member list relative to the anchor member. + * + * `origOwner` is the original direct owner of the top-level moved members. During ownership + * resolution, only members in `movedMembers` whose current owner equals `origOwner` have their + * `ownerRef` updated to the new owner (determined by the anchor and config). Members owned by + * nested sub-blocks are not re-owned — their `ownerRef` is assumed to already point to the + * correct intermediate owner. + * + * '''Descendants are NOT moved automatically.''' Only the explicitly listed `movedMembers` are + * repositioned in the flat list. Moving a `DFOwner` (e.g. a `StepBlock`) without its children + * breaks the pre-order DFS ownership invariant. Always include all descendants: + * {{{ + * val all = block :: block.members(MemberView.Flattened) + * anchor -> Patch.Move(all, block.getOwner, Patch.Move.Config.After) + * }}} + * + * '''`DFOwner` anchor + `After`/`InsideLast` redirect.''' When the anchor is a `DFOwner`, + * physical placement is redirected to `anchor.getVeryLastMember` (deepest recursive last + * descendant). Ownership resolution still uses the original anchor, so `newOwner = + * anchor.getOwnerBlock`. + * + * '''Concatenation.''' Multiple `Move` patches targeting the same anchor and config are merged + * in patchList order into a single Move before being applied to the member list. + * + * '''Conflict detection.''' This patch internally generates `Patch.Remove` for every member in + * `movedMembers`. If any of those members also appear in a separate patch in the same patchList + * (e.g. as the anchor of another `Move.Before`), an `IllegalArgumentException` is thrown. + * Resolve by splitting conflicting patches into sequential `db.patch()` calls. + */ final case class Move(movedMembers: List[DFMember], origOwner: DFOwner, config: Move.Config) extends Patch: override def toString(): String = @@ -119,14 +151,21 @@ object Patch: sealed trait Config extends Product with Serializable derives CanEqual: def ==(addConfig: Add.Config): Boolean = addConfig == this object Config: - // moves members before the patched member + // Moves members before the anchor member. The anchor must not be a DFOwner when + // members to be moved using Before are also referenced by other patches (e.g., as movedMembers + // in another Move), as the internal Remove generated here would conflict. case object Before extends Config - // moves members after the patched member + // Moves members after the anchor member. + // When the anchor is a DFOwner, placement is redirected to anchor.getVeryLastMember. + // Ownership update: newOwner = anchor.getOwnerBlock (NOT anchor itself). case object After extends Config - // moves members inside the given block, at the beginning + // Moves members inside the given block, at the beginning (after the block header). + // The anchor must be a DFOwner; members become direct children of the anchor. case object InsideFirst extends Config - // moves members inside the given block, at the end + // Moves members inside the given block, at the end. + // The anchor must be a DFOwner; redirects to anchor.getVeryLastMember for placement. case object InsideLast extends Config + end Config end Move final case class ChangeRef( @@ -172,6 +211,16 @@ extension (db: DB) .foldLeft(ReplacementContext.fromRefTable(refTable)) { case (rc, (origMember, Patch.Replace(repMember, config, refFilter))) if (origMember != repMember) => + // TODO: consider using this code in the future + // Include refs from any Add for this member so refs shared with added members (e.g. when + // the added block reuses the same member) are not purged and lost from the ref table. + // val addRefsForSameMember = + // patchList.collect { + // case (m, Patch.Add(db, _)) if m == origMember => db.members.flatMap(_.getRefs) + // }.flatten.toList + // val keepRefs = (repMember.getRefs ++ addRefsForSameMember).distinct + // val ret = + // rc.replaceMember(origMember, repMember, config, refFilter, keepRefs) val ret = rc.replaceMember(origMember, repMember, config, refFilter, repMember.getRefs) // patchDebug { @@ -182,16 +231,21 @@ extension (db: DB) case (rc, (origMember, Patch.Add(db, config))) => // if the original member is a global value, the all references pointing to the top design in the patch // db should point to an empty member for global placement - val fixedGlobalRefTable = origMember match - case dfVal: DFVal.CanBeGlobal if dfVal.isGlobal => + val globalPlacement = + (origMember, config) match + case (dfVal: DFVal.CanBeGlobal, _) if dfVal.isGlobal => true + case (design: DFDesignBlock, Patch.Add.Config.Before) if design.isTop => true + case _ => false + val fixedGlobalRefTable = + if (globalPlacement) db.refTable.map { case (ref, member) => if (member == db.top) (ref, DFMember.Empty) else (ref, member) } - case _ => db.refTable + else db.refTable // updating the patched DB reference table members with the newest members kept by the replacement context val updatedPatchRefTable = rc.getUpdatedRefTable(fixedGlobalRefTable) - val keepRefList = db.members.flatMap(_.getRefs) + lazy val keepRefList = db.members.flatMap(_.getRefs) val repRT = config match case Patch.Add.Config.ReplaceWithMemberN(n, repConfig, refFilter) => val repMember = db.members(n + 1) // At index 0 we have the Top. We don't want that. @@ -209,23 +263,19 @@ extension (db: DB) Patch.Replace.RefFilter.All, keepRefList ) case _ => rc - // patchDebug { - // println("repRT.refTable:") - // println(repRT.refTable.mkString("\n")) - // } - // patchDebug { - // println("dbPatched.refTable:") - // println(dbPatched.refTable.mkString("\n")) - // } - // patchDebug { - // println("updatedPatchRefTable:") - // println(updatedPatchRefTable.mkString("\n")) - // } + // patchDebug { + // println("repRT.refTable:") + // println(repRT.refTable.mkString("\n")) + // } + // patchDebug { + // println("updatedPatchRefTable:") + // println(updatedPatchRefTable.mkString("\n")) + // } val ret = repRT.copy(refTable = repRT.refTable ++ updatedPatchRefTable) - // patchDebug { - // println("rc.refTable:") - // println(ret.refTable.mkString("\n")) - // } + // patchDebug { + // println("rc.refTable:") + // println(ret.refTable.mkString("\n")) + // } ret // skip over empty move case (rc, (origMember, Patch.Move(Nil, _, config))) => rc @@ -338,6 +388,32 @@ extension (db: DB) Patch.Add(db2, Patch.Add.Config.After) ) => tbl + (m -> Patch.Add(db1 concat db2, config1)) + // concatenating additions with Before and After configurations, respectively + case ( + Patch.Add(db1, Patch.Add.Config.Before), + Patch.Add(db2, Patch.Add.Config.After) + ) => + val combinedDB = (db1 concat db2).copy(members = + db1.members ++ (m :: db2.members.drop(1)) + ) + val config = Patch.Add.Config.ReplaceWithMemberN( + db1.members.length - 1, + Patch.Replace.Config.FullReplacement + ) + tbl + (m -> Patch.Add(combinedDB, config)) + // concatenating additions with After and Before configurations, respectively + case ( + Patch.Add(db1, Patch.Add.Config.After), + Patch.Add(db2, Patch.Add.Config.Before) + ) => + val combinedDB = (db1 concat db2).copy(members = + (db1.members.head :: db2.members.drop(1)) ++ (m :: db1.members.drop(1)) + ) + val config = Patch.Add.Config.ReplaceWithMemberN( + db2.members.length - 1, + Patch.Replace.Config.FullReplacement + ) + tbl + (m -> Patch.Add(combinedDB, config)) // concatenating moves with the same configuration // (the patch table does not care about original owner, so we ignore it. // only the patchList that has all the move patches uses the original owners @@ -360,16 +436,20 @@ extension (db: DB) tbl + (m -> Patch.Add(add.db, Patch.Add.Config.ReplaceWithFirst())) // add followed by a replacement is allowed via a tandem patch execution case (add: Patch.Add, replace: Patch.Replace) if add.config == Patch.Add.Config.After => - tbl + (m -> Patch.Add( - add.db.copy(add.db.members.head :: replace.updatedMember :: add.db.members.drop(1)), - Patch.Add.Config.ReplaceWithFirst() - )) + tbl + + (m -> Patch.Add( + add.db.copy(add.db.members.head :: replace.updatedMember :: + add.db.members.drop(1)), + Patch.Add.Config.ReplaceWithFirst() + )) // replacement followed by an add via a tandem patch execution case (replace: Patch.Replace, add: Patch.Add) if add.config == Patch.Add.Config.After => - tbl + (m -> Patch.Add( - add.db.copy(add.db.members.head :: replace.updatedMember :: add.db.members.drop(1)), - Patch.Add.Config.ReplaceWithFirst() - )) + tbl + + (m -> Patch.Add( + add.db.copy(add.db.members.head :: replace.updatedMember :: + add.db.members.drop(1)), + Patch.Add.Config.ReplaceWithFirst() + )) // allow the same member to be removed more than once by getting rid of the redundant removals case (Patch.Remove(isMovedL), Patch.Remove(isMovedR)) => tbl + (m -> Patch.Remove(isMovedL || isMovedR)) diff --git a/core/src/main/scala/dfhdl/compiler/patching/ReplacementContext.scala b/core/src/main/scala/dfhdl/compiler/patching/ReplacementContext.scala index 0e8c902e4..8c3d6a269 100644 --- a/core/src/main/scala/dfhdl/compiler/patching/ReplacementContext.scala +++ b/core/src/main/scala/dfhdl/compiler/patching/ReplacementContext.scala @@ -99,11 +99,14 @@ private final case class ReplacementContext( def removeMember(origMember: DFMember): ReplacementContext = // Total references to be removed are: - // * refs from memberTable - references from other members to this member + // * refs from memberTable that still point to origMember in refTable (references from other + // members to this member; may have been reassigned by a prior Replace(ChangeRefOnly)) // * refs from origMember - references from this member to other members // Together these cover both directions of two-way references + val refsToOrigThatStillPointHere = + memberTable.getOrElse(origMember, Set()).filter(r => refTable.get(r).contains(origMember)) val purgedRefs = - memberTable.getOrElse(origMember, Set()) ++ origMember.getRefs + origMember.ownerRef + refsToOrigThatStillPointHere ++ origMember.getRefs + origMember.ownerRef val (updatedTypeRefRepeats, purgedRefsWithoutRepeatedTypeRefs) = getUpdatedTypeRefCount(purgedRefs) val updatedMemberTable = purgedRefsWithoutRepeatedTypeRefs.foldLeft(memberTable) { diff --git a/core/src/main/scala/dfhdl/core/DFC.scala b/core/src/main/scala/dfhdl/core/DFC.scala index b2faa467b..baf8f12db 100644 --- a/core/src/main/scala/dfhdl/core/DFC.scala +++ b/core/src/main/scala/dfhdl/core/DFC.scala @@ -65,6 +65,10 @@ final case class DFC( def lateConstruction: Boolean = mutableDB.OwnershipContext.lateConstruction def ownerOption: Option[DFOwnerAny] = mutableDB.OwnershipContext.ownerOption.map(_.asFE) + // Returns the IR ref for the current owner, or a ref to DFMember.Empty when there is no + // owner in the context. Prefer this over `dfc.owner.ref` when constructing raw IR members + // (e.g. ir.Goto) inside a MetaDesign body: the `ref` extension method requires + // `import dfhdl.core.*` in scope, which can conflict with other `dfhdl.core` imports. def ownerOrEmptyRef: ir.DFOwner.Ref = ownerOption.map(_.asIR.ref(using this)).getOrElse(ir.DFMember.Empty.ref(using this)) def setName(name: String): this.type = diff --git a/core/src/main/scala/dfhdl/core/DFDecimal.scala b/core/src/main/scala/dfhdl/core/DFDecimal.scala index 9d72011ca..d2a3e33f0 100644 --- a/core/src/main/scala/dfhdl/core/DFDecimal.scala +++ b/core/src/main/scala/dfhdl/core/DFDecimal.scala @@ -978,16 +978,38 @@ object DFXInt: if (dfType.signed != lhsSigned && !lhs.dfType.asIR.isDFInt32) lhs.asValOf[DFUInt[Int]].signed.asValOf[DFSInt[Int]] else lhs.asValOf[DFSInt[Int]] - val nativeTypeChanged = dfType.nativeType != lhs.dfType.nativeType + // Auto-promote anonymous +/-/* to carry when target is wide enough + import IntParam.+ + val funcWidth = lhsSignFix.widthIntParam + val lhsCarryPromo: DFValOf[DFSInt[Int]] = + lhsSignFix.asIR match + case func @ ir.DFVal.Func( + dfType = dt: ir.DFDecimal, + op = op @ (FuncOp.+ | FuncOp.- | FuncOp.*) + ) + if func.isAnonymous && !dt.isDFInt32 && dfType.widthInt > func.width => + val cw: IntParam[Int] = func.op.runtimeChecked match + case FuncOp.+ | FuncOp.- => funcWidth + 1 + case FuncOp.* => funcWidth + funcWidth + val newDT = dt.copy(widthParamRef = cw.ref) + dfc.mutableDB + .setMember(func, _.updateDFType(newDT)) + .asValOf[DFSInt[Int]] + case _ => lhsSignFix + val nativeTypeChanged = dfType.nativeType != lhsCarryPromo.dfType.nativeType if (nativeTypeChanged) dfType.asIR.nativeType match case Int32 => - lhsSignFix.toInt.asIR + lhsCarryPromo.toInt.asIR case BitAccurate => - DFVal.Alias.AsIs(dfType, lhsSignFix).asIR - else if (!dfType.asIR.widthParamRef.isSimilarTo(lhsSignFix.dfType.asIR.widthParamRef)) - lhsSignFix.resize(dfType.widthIntParam).asIR - else lhsSignFix.asIR + DFVal.Alias.AsIs(dfType, lhsCarryPromo).asIR + else if ( + !dfType.asIR.widthParamRef.isSimilarTo( + lhsCarryPromo.dfType.asIR.widthParamRef + ) + ) + lhsCarryPromo.resize(dfType.widthIntParam).asIR + else lhsCarryPromo.asIR end if end if end dfValIR @@ -1045,7 +1067,8 @@ object DFXInt: end arithOp type ArithOp = - FuncOp.+.type | FuncOp.-.type | FuncOp.*.type | FuncOp./.type | FuncOp.%.type | FuncOp.max.type | FuncOp.min.type + FuncOp.+.type | FuncOp.-.type | FuncOp.*.type | FuncOp./.type | FuncOp.%.type | + FuncOp.max.type | FuncOp.min.type given evOpArithIntDFInt32[ Op <: ArithOp, L <: Int, diff --git a/core/src/main/scala/dfhdl/core/DFFor.scala b/core/src/main/scala/dfhdl/core/DFFor.scala index 7effa7c86..7e05834d3 100644 --- a/core/src/main/scala/dfhdl/core/DFFor.scala +++ b/core/src/main/scala/dfhdl/core/DFFor.scala @@ -26,7 +26,7 @@ object DFFor: )(using DFC): Unit = val iter = DFVal.Dcl.iterator(using dfc.setMeta(iterMeta)) dfc.mutableDB.DesignContext.addLoopIter(iterMeta, iter) - val block = Block(iter, range)(using dfc.setMetaAnon(forPos)) + val block = Block(iter, range)(using dfc.setMeta(position = forPos)) dfc.enterOwner(block) guards.foreach { guard => val guardVal = guard() diff --git a/core/src/main/scala/dfhdl/core/DFVal.scala b/core/src/main/scala/dfhdl/core/DFVal.scala index a2c30e686..5984877c7 100644 --- a/core/src/main/scala/dfhdl/core/DFVal.scala +++ b/core/src/main/scala/dfhdl/core/DFVal.scala @@ -642,20 +642,21 @@ object DFVal extends DFValLP: end Const object DesignParam: + // Note: in meta-programming, the user needs to manually set the Design's paramMap. def apply[T <: DFTypeAny]( - dfVal: DFValOf[T], - default: Option[DFValOf[T]] = None + appliedVal: DFValOf[T], + defaultVal: Option[DFValOf[T]] = None )(using DFC): DFConstOf[T] = val alias: ir.DFVal.DesignParam = ir.DFVal.DesignParam( - dfVal.asIR.dfType.dropUnreachableRefs, - dfVal.asIR.refTW[ir.DFVal.DesignParam](knownReachable = true), - default.map(_.asIR.refTW[ir.DFVal.DesignParam]) + appliedVal.asIR.dfType.dropUnreachableRefs, + defaultVal.map(_.asIR.refTW[ir.DFVal.DesignParam]) .getOrElse(ir.DFMember.Empty.refTW[ir.DFVal.DesignParam]), dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags ) + if (!dfc.inMetaProgramming) alias.setCachedAppliedVal(appliedVal.asIR) alias.addMember.asConstOf[T] end apply end DesignParam @@ -774,8 +775,13 @@ object DFVal extends DFValLP: if (prevLHSArg.isAnonymous) Some(prevLHSArg.setMeta(_ => dfc.getMeta)) else Some(prevLHSArg) else - dfc.mutableDB.setMember(prevRHSArg, _.copy(data = Some(newRHSData))) - Some(dfc.mutableDB.setMember(prevFunc, _.copy(meta = dfc.getMeta))) + // Clone prevFunc to avoid destructively modifying shared IR nodes + val clonedFunc = prevFunc.cloneAnonValueAndDepsHere + .asInstanceOf[ir.DFVal.Func] + val clonedRHSArg = clonedFunc.args.last.get + .asInstanceOf[ir.DFVal.Const] + dfc.mutableDB.setMember(clonedRHSArg, _.copy(data = Some(newRHSData))) + Some(dfc.mutableDB.setMember(clonedFunc, _.copy(meta = dfc.getMeta))) case _ => None end match case _ => None @@ -830,9 +836,12 @@ object DFVal extends DFValLP: ) ).asVal[AT, M] // remove redundant intermediate casting when the final result needs to be `.bits` anyways - case asIs: ir.DFVal.Alias.AsIs + // as long as the alias is anonymous and has the same width as the related value, + // to avoid modifying the semantics of named values that can be referenced in multiple places. + case asIs @ ir.DFVal.Alias.AsIs(relValRef = ir.DFRef(relValIR)) if aliasType.asIR.isInstanceOf[ir.DFBits] && asIs.isAnonymous && - dfc.isAnonymous && !forceNewAlias && asIs.tags.isEmpty => + dfc.isAnonymous && !forceNewAlias && asIs.tags.isEmpty && + relValIR.width == asIs.width => asIs.relValRef.get.asVal[AT, M] // remove redundant intermediate casting converting from BoolOrBit to Bits/UInt/SInt + resize case asIs @ ir.DFVal.Alias.AsIs( @@ -1869,8 +1878,8 @@ extension (dfVal: ir.DFVal) else dfVal match // design parameter, so recurse on the referenced value - case ir.DFVal.DesignParam(dfValRef = ir.DFRef(of)) => - of.cloneUnreachable + case dp: ir.DFVal.DesignParam => + dp.appliedOrDefaultVal.cloneUnreachable // named constant, so clone under a new name within relation to the current design case _ => val newMeta = diff --git a/core/src/main/scala/dfhdl/core/DFWhile.scala b/core/src/main/scala/dfhdl/core/DFWhile.scala index e14009bf2..29c251dfd 100644 --- a/core/src/main/scala/dfhdl/core/DFWhile.scala +++ b/core/src/main/scala/dfhdl/core/DFWhile.scala @@ -2,6 +2,7 @@ package dfhdl.core import dfhdl.compiler.ir import dfhdl.internals.* import scala.annotation.implicitNotFound +import scala.reflect.ClassTag object DFWhile: object Block: @@ -21,27 +22,40 @@ object DFWhile: dfc.exitOwner() end DFWhile -//to be used inside an RT loop to indicate that the loop is combinational -def COMB_LOOP(using - dfc: DFC, - @implicitNotFound( - "`COMB_LOOP` is only allowed under register-transfer (RT) domains." - ) rt: DomainType.RT -): Unit = - import dfc.getSet - var ownerIR = dfc.owner.asIR - var stop = false - var lineEnd = -1 - while (!stop) - ownerIR match - case cb: ir.DFConditional.Block => ownerIR = cb.getOwner - case lb: ir.DFLoop.Block => - if (lineEnd == -1) - lineEnd = lb.meta.position.lineEnd - else if (lineEnd != lb.meta.position.lineEnd) - stop = true - if (!stop) - ownerIR.setTags(_.tag(ir.CombinationalTag)) - ownerIR = lb.getOwner - case _ => stop = true -end COMB_LOOP +protected[dfhdl] object LoopOps: + private def loopTag[CT <: ir.DFTag: ClassTag](tag: CT)(using DFC): Unit = + import dfc.getSet + var ownerIR = dfc.owner.asIR + var stop = false + var lineEnd = -1 + while (!stop) + ownerIR match + case cb: ir.DFConditional.Block => ownerIR = cb.getOwner + case lb: ir.DFLoop.Block => + if (lineEnd == -1) + lineEnd = lb.meta.position.lineEnd + else if (lineEnd != lb.meta.position.lineEnd) + stop = true + if (!stop) + ownerIR.setTags(_.tag(tag)) + ownerIR = lb.getOwner + case _ => stop = true + end loopTag + + // to be used inside an RT loop to indicate that the loop is combinational + def COMB_LOOP(using + dfc: DFC, + @implicitNotFound( + "`COMB_LOOP` is only allowed under register-transfer (RT) domains." + ) rt: DomainType.RT + ): Unit = loopTag(ir.CombinationalTag) + + // to be used inside an RT loop to indicate that the loop should fall through to the + // next step if the guard is false without consuming any cycles + def FALL_THROUGH(using + dfc: DFC, + @implicitNotFound( + "`FALL_THROUGH` is only allowed under register-transfer (RT) domains." + ) rt: DomainType.RT + ): Unit = loopTag(ir.FallThroughTag) +end LoopOps diff --git a/core/src/main/scala/dfhdl/core/Design.scala b/core/src/main/scala/dfhdl/core/Design.scala index 2127c2bff..1b167dda4 100644 --- a/core/src/main/scala/dfhdl/core/Design.scala +++ b/core/src/main/scala/dfhdl/core/Design.scala @@ -54,6 +54,7 @@ trait Design extends Container, HasClsMetaArgs: final override def onCreateStartLate: Unit = hasStartedLate = true import dfc.getSet + Design.Block.updateWithParams(containedOwner.asIR) if (dfc.owner.asIR.getThisOrOwnerDesign.isDeviceTop) handleResourceConstraints() dfc.mutableDB.ResourceOwnershipContext.emptyTopResourceOwners() @@ -152,12 +153,28 @@ object Design: type Block = DFOwner[ir.DFDesignBlock] object Block: def apply(domain: ir.DomainType, dclMeta: ir.Meta, instMode: InstMode)(using DFC): Block = - ir.DFDesignBlock( - domain, dclMeta, instMode, dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags + val paramMap = ListMap.from( + dfc.mutableDB.DesignContext.getDesignParamValueMap.view.mapValues( + _.asIR.refTW[ir.DFDesignBlock] + ) ) - .addMember - .asFE + ir.DFDesignBlock( + domain, dclMeta, instMode, paramMap, dfc.ownerOrEmptyRef, dfc.getMeta, dfc.tags + ).addMember.asFE end apply + protected[core] def updateWithParams(designBlock: ir.DFDesignBlock)(using dfc: DFC): Unit = + import dfc.getSet + val paramMap = + ListMap.from( + dfc.mutableDB.DesignContext.current.getImmutableMemberList.view.collect { + case dp: ir.DFVal.DesignParam => + val dfVal = dp.appliedValOpt.get + // invalidating the param cache value after design elaboration + dp.clearCachedAppliedVal() + dp.getName -> dfVal.refTW[ir.DFDesignBlock](knownReachable = true) + }.toMap + ) + getSet.replace(designBlock)(designBlock.copy(paramMap = paramMap)) end Block extension [D <: Design](dsn: D) def getDB: ir.DB = dsn.dfc.mutableDB.immutable diff --git a/core/src/main/scala/dfhdl/core/MutableDB.scala b/core/src/main/scala/dfhdl/core/MutableDB.scala index 4bc6dc3c4..5e95d8bbf 100644 --- a/core/src/main/scala/dfhdl/core/MutableDB.scala +++ b/core/src/main/scala/dfhdl/core/MutableDB.scala @@ -28,6 +28,7 @@ import dfhdl.compiler.analysis.filterPublicMembers import scala.reflect.{ClassTag, classTag} import collection.mutable +import collection.immutable.ListMap private case class MemberEntry( irValue: DFMember, @@ -168,6 +169,17 @@ final class MutableDB(): var stack = List.empty[DesignContext] val designMembers = mutable.Map.empty[DFDesignBlock, List[DFMember]] val uniqueDesigns = mutable.Map.empty[String, List[List[DFDesignBlock]]] + + // for design parameters we save them via the plugin before the design is elaborated, and then + // construct the design block with the parameters referenced in its paramMap. + private var designParamValueMap = ListMap.empty[String, DFValAny] + def prepareDesignParamValues(paramNames: List[String], paramValues: List[DFValAny]): Unit = + designParamValueMap = ListMap.from(paramNames.lazyZip(paramValues)) + def getDesignParamValueMap: ListMap[String, DFValAny] = + val ret = designParamValueMap + designParamValueMap = ListMap.empty + ret + def startDesign(design: DFDesignBlock): Unit = stack = current :: stack current = new DesignContext @@ -178,10 +190,7 @@ final class MutableDB(): var isDuplicate = false def sameDesignAs(groupDesign: DFDesignBlock): Boolean = if (design.dclMeta == groupDesign.dclMeta) - val groupMembers = designMembers(groupDesign) - if (currentMembers.length == groupMembers.length) - (currentMembers lazyZip groupMembers).forall { case (l, r) => l =~ r } - else false + currentMembers =~ designMembers(groupDesign) else false uniqueDesigns.get(designType) match // this design type already exists and has at least one group @@ -201,21 +210,25 @@ final class MutableDB(): // first time encountering this design type, so add the first group case None => uniqueDesigns += designType -> List(List(design)) end match - // generally, if this design is a duplicate we want to add only the by-name members and their - // owner references. however, if current design context is known to be a duplicate (as a result - // of a `hw.pure` annotation), then we can skip this extra step since the design context is - // already minimized to the named members. + // If this design is a duplicate, we retain only the public members (ports, design + // parameters, domain blocks, and their dependencies) during elaboration, because + // user code may still reference them (e.g., connecting to a port requires the Dcl + // before a PortByNameSelect is created). These public members are later removed + // during immutable DB creation (see `immutable`), where ports are resolved + // on-demand via DuplicationRef in `DB.dupPortsByName`. + // If the current design context is already known to be a duplicate (as a result + // of a `hw.pure` annotation), then we can skip this extra step since the design + // context is already minimized to the named members. if (isDuplicate && !current.isDuplicate) - // public members are ports, design design parameters, and - // design domains. for design parameters we also get dependencies. - // all these members are interacted with outside the design, - // so they are kept as duplicates in the design instances val publicMembers = currentMembers.filterPublicMembers designMembers += design -> publicMembers - val transferredRefs = publicMembers.view.flatMap(m => - (m.ownerRef -> currentRefTable(m.ownerRef)) :: - m.getRefs.map(r => r -> currentRefTable(r)) - ) + val transferredRefs = + // getting the design references to parameters + design.getRefs.map(r => r -> currentRefTable(r)) ++ + publicMembers.view.flatMap(m => + (m.ownerRef -> currentRefTable(m.ownerRef)) :: + m.getRefs.map(r => r -> currentRefTable(r)) + ) stack.head.refTable ++= transferredRefs else designMembers += design -> currentMembers @@ -373,8 +386,9 @@ final class MutableDB(): else resource.allSigConstraints } // merge the existing constraints with the new constraints - val updatedSigConstraints = - (existingSigConstraints ++ newSigConstraints).merge.consolidate(dcl.width) + val updatedSigConstraints = (existingSigConstraints ++ newSigConstraints).merge.consolidate( + dcl.width + ) // merge all other annotations val updatedAnnotations = updatedSigConstraints ++ otherAnnotations dcl -> dcl.copy(meta = dcl.meta.copy(annotations = updatedAnnotations)) @@ -568,6 +582,7 @@ final class MutableDB(): case (ref: DFRef.TypeRef, m) => usedTypeRefs.contains(ref) case _ => true }.toMap + val duplicateDesignSet = mutable.Set.empty[DFDesignBlock] val duplicateDesignRepMap = DesignContext.uniqueDesigns.view.flatMap { case (designType, groupList) => groupList.view.reverse.zipWithIndex.flatMap { @@ -581,7 +596,9 @@ final class MutableDB(): if (first) first = false design.tags - else design.tags.tag(DuplicateTag) + else + duplicateDesignSet += design + design.tags.tag(DuplicateTag) design -> design.copy( dclMeta = design.dclMeta.copy(nameOpt = Some(updatedDclName)), tags = tags @@ -609,7 +626,24 @@ final class MutableDB(): case dcl: DFVal.Dcl => constrainedDcls.getOrElse(dcl, dcl) case m => m } - (members.map(finalFixFunc), fixedRefTable.view.mapValues(finalFixFunc).toMap) + // Remove all remaining public members (ports, domain blocks, and their + // dependencies) from duplicate designs. During elaboration these were kept + // so user code could reference them, but in the immutable DB they are no + // longer needed. Ports for duplicate designs are resolved on-demand via + // DuplicationRef in `DB.dupPortsByName`. + val redundantRefs = mutable.Set.empty[DFRefAny] + val finalMembers = members.flatMap { + case m: DFVal if m.isGlobal => Some(finalFixFunc(m)) + case m: (DomainBlock | DFVal) if duplicateDesignSet.contains(m.getOwnerDesign) => + redundantRefs += m.ownerRef + redundantRefs ++= m.getRefs + None + case m => Some(finalFixFunc(m)) + } + val finalRefTable = fixedRefTable.view.flatMap { case (ref, member) => + if (redundantRefs.contains(ref)) None else Some(ref -> finalFixFunc(member)) + }.toMap + (finalMembers, finalRefTable) val membersNoGlobalCtx = members.map { case m: DFVal.CanBeGlobal => m.copyWithoutGlobalCtx case m => m diff --git a/core/src/main/scala/dfhdl/core/Step.scala b/core/src/main/scala/dfhdl/core/Step.scala index 0d0b04842..0844a5c7b 100644 --- a/core/src/main/scala/dfhdl/core/Step.scala +++ b/core/src/main/scala/dfhdl/core/Step.scala @@ -56,41 +56,52 @@ object Step extends Step: scope: DFC.Scope.Process ): Unit = val step = StepBlock(using dfc.setMeta(stepMeta)) - scope.stepCache += (stepMeta.name -> step.asIR) + // a unique step key is generated by combining the step's name with its starting line number. + // This handles cases where step names are reused in different internal scopes, such as within loops or nested steps. + val stepKey = s"${stepMeta.name}_${stepMeta.position.lineStart}" + scope.stepCache += (stepKey -> step.asIR) // this is called by the compiler plugin and replaces the step's `def`. this will add the // step to the context, update its reference to point to the proper owner, and finally run // the step's body with the step as its owner. - def pluginAddStep(stepName: String)( + def pluginAddStep(stepKey: String)( run: => Unit )(using dfc: DFC, scope: DFC.Scope.Process): Unit = - val stepIR = scope.stepCache(stepName) + val stepIR = scope.stepCache(stepKey) stepIR.addMember dfc.mutableDB.newRefFor(stepIR.ownerRef, dfc.owner.asIR) dfc.enterOwner(stepIR.asFE) run dfc.exitOwner() - // this is called by the compiler plugin and replaces the step's onEntry/onExit `def` with - // a step block that has the name "onEntry"/"onExit" which is special-cases and not treated - // as a normal step, but just a container for the onEntry/onExit code. - def pluginOnEntryExit(meta: ir.Meta)( - run: => Unit + // this is called by the compiler plugin and replaces the step's onEntry/onExit/fallThrough `def` with + // a step block that has the name "onEntry"/"onExit"/"fallThrough" which is special-cases and not treated + // as a normal step, but just a container for the onEntry/onExit/fallThrough code. + def pluginOnEntryExitFallThrough(meta: ir.Meta)( + run: => Any )(using dfc: DFC): Unit = - val onEntryExit = ir.StepBlock( + import dfc.getSet + val onEntryExitFallThrough = ir.StepBlock( dfc.owner.ref, meta, dfc.tags ).addMember - dfc.enterOwner(onEntryExit.asFE) - run + dfc.enterOwner(onEntryExitFallThrough.asFE) + val ret = run + if (onEntryExitFallThrough.isFallThrough) + Exact.strip(ret) match + case v: DFValAny => + // adding ident placement as the last member of the fall-through block + DFVal.Alias.AsIs.ident(v)(using dfc.anonymize) + case _ => // do nothing dfc.exitOwner() + end pluginOnEntryExitFallThrough // this is called by the compiler plugin and replaces references (calls) to the step `def`. // for the process this is considered as a goto statement. - def pluginGotoStep(nextStepName: String)(using dfc: DFC, scope: DFC.Scope.Process): Unit = + def pluginGotoStep(nextStepKey: String)(using dfc: DFC, scope: DFC.Scope.Process): Unit = import dfc.getSet - val nextStep = scope.stepCache(nextStepName) + val nextStep = scope.stepCache(nextStepKey) val member: ir.Goto = ir.Goto( nextStep.refTW[ir.Goto], dfc.owner.ref, diff --git a/core/src/main/scala/dfhdl/core/TextOut.scala b/core/src/main/scala/dfhdl/core/TextOut.scala index 99a09eae3..7f59a6d5e 100644 --- a/core/src/main/scala/dfhdl/core/TextOut.scala +++ b/core/src/main/scala/dfhdl/core/TextOut.scala @@ -42,33 +42,35 @@ object TextOut: transparent inline def print(inline msg: Any): Unit = compiletime.summonFrom { case given ScalaPrintsFlag => scala.Predef.print(msg) - case given DFC.Scope.Local => textOut(Op.Print, Some(msg)) - case _ => scala.Predef.print(msg) + case given DFC.Scope.Local => + textOut(Op.Print, Some(msg))(using compiletime.summonInline[DFC]) + case _ => scala.Predef.print(msg) } transparent inline def println(inline msg: Any): Unit = compiletime.summonFrom { case given ScalaPrintsFlag => scala.Predef.println(msg) - case given DFC.Scope.Local => textOut(Op.Println, Some(msg)) - case _ => scala.Predef.println(msg) + case given DFC.Scope.Local => + textOut(Op.Println, Some(msg))(using compiletime.summonInline[DFC]) + case _ => scala.Predef.println(msg) } transparent inline def println(): Unit = compiletime.summonFrom { case given ScalaPrintsFlag => scala.Predef.println() - case given DFC.Scope.Local => textOut(Op.Println, None) + case given DFC.Scope.Local => textOut(Op.Println, None)(using compiletime.summonInline[DFC]) case _ => scala.Predef.println() } inline def report(inline message: Any, severity: Severity = Severity.Info): Unit = - textOut(Op.Report(severity), Some(message)) + textOut(Op.Report(severity), Some(message))(using compiletime.summonInline[DFC]) inline def assert( inline assertion: Any, inline message: Any, severity: Severity )(using dfc: DFC): Unit = - assertDFHDL(assertion, Some(message), severity) + assertDFHDL(assertion, Some(message), severity)(using compiletime.summonInline[DFC]) transparent inline def assert(inline assertion: Any, inline message: => Any): Unit = compiletime.summonFrom { @@ -118,11 +120,11 @@ object TextOut: private inline def textOut( op: ir.TextOut.Op, inline msgOption: Option[Any] - ): Unit = ${ textOutMacro('op, 'msgOption) } + )(using dfc: DFC): Unit = ${ textOutMacro('op, 'msgOption)('dfc) } private def textOutMacro( op: Expr[ir.TextOut.Op], msgOption: Expr[Option[Any]] - )(using + )(dfc: Expr[DFC])(using Quotes ): Expr[Unit] = import quotes.reflect.* @@ -133,7 +135,6 @@ object TextOut: case _ => t var msgPartsExpr: Expr[List[String]] = '{ List.empty[String] } var msgArgsExpr: Expr[List[DFValAny]] = '{ List.empty[DFValAny] } - val dfc = Expr.summon[DFC].get recurse(msgOption.asTerm).asExpr match case '{ None } => case '{ Some($msg) } => diff --git a/core/src/main/scala/dfhdl/core/r__For_Plugin.scala b/core/src/main/scala/dfhdl/core/r__For_Plugin.scala index d6adfdd9d..87cc92d12 100644 --- a/core/src/main/scala/dfhdl/core/r__For_Plugin.scala +++ b/core/src/main/scala/dfhdl/core/r__For_Plugin.scala @@ -104,15 +104,19 @@ object r__For_Plugin: Pattern.BindSI(op, parts, bindVals.map(_.asIR.refTW[DFConditional.DFCaseBlock])) @metaContextIgnore def genDesignParam[V <: DFValAny]( - paramValue: DFValAny, - default: Option[DFValAny], + appliedVal: DFValAny, + defaultVal: Option[DFValAny], paramMeta: ir.Meta )(using DFC): V = trydf: dfc.mutableDB.DesignContext.getReachableNamedValue( - paramValue.asIR, - DFVal.DesignParam(paramValue, default)(using dfc.setMeta(paramMeta)).asIR + appliedVal.asIR, + DFVal.DesignParam(appliedVal, defaultVal)(using dfc.setMeta(paramMeta)).asIR ).asValAny.asInstanceOf[V] + def prepareDesignParamValues(paramNames: List[String], paramValues: List[DFValAny])(using + DFC + ): Unit = + dfc.mutableDB.DesignContext.prepareDesignParamValues(paramNames, paramValues) @metaContextIgnore def designFromDefGetInput[V <: DFValAny](idx: Int)(using DFC): V = dfc.mutableDB.DesignContext.getDefInput(idx).asInstanceOf[V] @@ -135,6 +139,7 @@ object r__For_Plugin: } val (isDuplicate, ret): (Boolean, V) = dfc.mutableDB.DesignContext.runFuncWithInputs(func, inputs) + Design.Block.updateWithParams(designBlock.asIR) def exitAndConnectInputs() = dfc.exitOwner() inputs.lazyZip(args).foreach { case (input, (arg, _)) => @@ -157,4 +162,6 @@ object r__For_Plugin: output.asInstanceOf[V] end if end designFromDef + def identVal[V <: DFValAny](value: V)(using DFC): V = + DFVal.Alias.AsIs.ident(value).asInstanceOf[V] end r__For_Plugin diff --git a/core/src/main/scala/dfhdl/hdl.scala b/core/src/main/scala/dfhdl/hdl.scala index f02b2fe13..61ee08459 100644 --- a/core/src/main/scala/dfhdl/hdl.scala +++ b/core/src/main/scala/dfhdl/hdl.scala @@ -24,7 +24,7 @@ protected object hdl: export internals.CommonOps.* export core.{dfType} export core.DFPhysical.Val.Ops.* - export core.COMB_LOOP + export core.LoopOps.* type Time = core.DFTime val Time = core.DFTime type Freq = core.DFFreq diff --git a/core/src/main/scala/dfhdl/options/OnError.scala b/core/src/main/scala/dfhdl/options/OnError.scala index c022736f5..12e7ef5b0 100644 --- a/core/src/main/scala/dfhdl/options/OnError.scala +++ b/core/src/main/scala/dfhdl/options/OnError.scala @@ -1,7 +1,7 @@ package dfhdl.options -import dfhdl.internals.{sbtShellIsRunning, sbtTestIsRunning} +import dfhdl.internals.{sbtShellIsRunning, sbtTestIsRunning, sbtnIsRunning} enum OnError derives CanEqual: case Exit, Exception object OnError: - given OnError = if (sbtShellIsRunning || sbtTestIsRunning) Exception else Exit + given OnError = if (sbtShellIsRunning || sbtnIsRunning || sbtTestIsRunning) Exception else Exit diff --git a/core/src/test/scala/CoreSpec/DFDecimalSpec.scala b/core/src/test/scala/CoreSpec/DFDecimalSpec.scala index 03266c499..675208ce3 100644 --- a/core/src/test/scala/CoreSpec/DFDecimalSpec.scala +++ b/core/src/test/scala/CoreSpec/DFDecimalSpec.scala @@ -598,6 +598,56 @@ class DFDecimalSpec extends DFSpec: """u8 % d"9'22"""" ) } + test("Arithmetic auto-carry promotion") { + val u8 = UInt(8) <> VAR + val u5 = UInt(5) <> VAR + val s8 = SInt(8) <> VAR + val u8b = UInt(8) <> VAR + val u9 = UInt(9) <> VAR + val u10 = UInt(10) <> VAR + val u12 = UInt(12) <> VAR + val u16 = UInt(16) <> VAR + val s9 = SInt(9) <> VAR + assertCodeString { + """|u9 := u8 +^ u8 + |u9 := u8 -^ u8 + |u16 := u8 *^ u8 + |u10 := (u8 +^ u8).resize(10) + |u8b := u8 + u8 + |val sum = u8 + u8 + |u9 := sum.resize(9) + |s9 := s8 +^ s8 + |u9 := (u8 / u8).resize(9) + |u9 := u8 +^ u5.resize(8) + |u9 := u8 +^ d"8'200" + |u12 := (u8 *^ u8).resize(12) + |""".stripMargin + } { + // Basic carry promotion for + + u9 := u8 + u8 + // Basic carry promotion for - + u9 := u8 - u8 + // Basic carry promotion for * + u16 := u8 * u8 + // Target wider than carry width: promote to 9, resize to 10 + u10 := u8 + u8 + // Target = func width: no promotion + u8b := u8 + u8 + // Named value: no promotion + val sum = u8 + u8 + u9 := sum + // SInt version + s9 := s8 + s8 + // Division: no carry variant, normal resize + u9 := u8 / u8 + // Asymmetric widths: u8 + u5 → func width 8, carry = 9 + u9 := u8 + u5 + // Int literal: 200 is 8 bits, carry width = 9 + u9 := u8 + 200 + // Partial mul promotion: target (12) > funcWidth (8), promote to 16, resize to 12 + u12 := u8 * u8 + } + } test("Int32 arithmetic") { val param: Int <> CONST = 2 val t1 = 1 + param diff --git a/core/src/test/scala/CoreSpec/DFVectorSpec.scala b/core/src/test/scala/CoreSpec/DFVectorSpec.scala index bd3772e26..a368935d3 100644 --- a/core/src/test/scala/CoreSpec/DFVectorSpec.scala +++ b/core/src/test/scala/CoreSpec/DFVectorSpec.scala @@ -24,7 +24,8 @@ class DFVectorSpec extends DFSpec: |val v3 = UInt(8) X 4 X 4 <> VAR |v3 := all(all(d"8'0")) |v3(3)(1) := d"8'25" - |v3 := v3 + |val t = v3 + |v3 := t |val len: Int <> CONST = 3 |val v4 = UInt(8) X len <> VAR init all(d"8'0") |val v5: UInt[4] X len.type <> CONST = all(d"4'0") diff --git a/docs/css/user-guide.css b/docs/css/user-guide.css index 036b10233..313ade59a 100644 --- a/docs/css/user-guide.css +++ b/docs/css/user-guide.css @@ -1,3 +1,10 @@ +/* column separator between the two halves of the conversion table */ +div.conversion td:nth-child(4), +div.conversion th:nth-child(4) { + border-left: 2px solid var(--md-typeset-table-color, #0000001f); + padding-left: 1em; +} + /* different styles for tabs inside tabs */ html.js-focus-visible.js body div.md-container main.md-main div.md-main__inner.md-grid div.md-content article.md-content__inner.md-typeset div.admonition div.tabbed-set.tabbed-alternate div.tabbed-content div.tabbed-block div.tabbed-set.tabbed-alternate div.tabbed-labels.tabbed-labels--linked label a { font-size: smaller; diff --git a/docs/getting-started/hello-world/index.md b/docs/getting-started/hello-world/index.md index aceae8d67..948e256d1 100644 --- a/docs/getting-started/hello-world/index.md +++ b/docs/getting-started/hello-world/index.md @@ -73,6 +73,23 @@ scala run . For more information, please run `scala run --help` or consult the [online documentation](https://scala-cli.virtuslab.org/docs/commands/run){target="_blank"}. +/// admonition | Multi-file projects + type: tip +In a scala-cli project with multiple `.scala` files, shared `given` declarations (such as compiler options) must appear in exactly one file. Place them in your `project.scala` file to avoid duplicate definition errors. + +```scala title="project.scala" +//> using scala 3.8.1 +//> using dep io.github.dfianthdl::dfhdl::0.17.0 +//> using plugin io.github.dfianthdl:::dfhdl-plugin:0.17.0 + +import dfhdl.* +given options.CompilerOptions.Backend = backends.verilog +given options.CompilerOptions.PrintBackendCode = true +``` + +Individual design files should `import dfhdl.*` but not redeclare the shared `given` options. +/// + ### sbt Project The best way to get started with a DFHDL sbt project is to clone our template from GitHub: diff --git a/docs/getting-started/hello-world/scala-project/.scalafmt.conf b/docs/getting-started/hello-world/scala-project/.scalafmt.conf index fcc9a13d3..75af615b0 100644 --- a/docs/getting-started/hello-world/scala-project/.scalafmt.conf +++ b/docs/getting-started/hello-world/scala-project/.scalafmt.conf @@ -1,4 +1,4 @@ -version = 3.10.5 +version = 3.10.7 runner.dialect = scala3 maxColumn = 100 diff --git a/docs/transitioning/from-verilog/index.md b/docs/transitioning/from-verilog/index.md index 271e4882e..779cb3749 100644 --- a/docs/transitioning/from-verilog/index.md +++ b/docs/transitioning/from-verilog/index.md @@ -1,10 +1,8 @@ # Transitioning from Verilog to DFHDL -## Using ChatGPT +This guide helps Verilog/SystemVerilog users translate common patterns into DFHDL. For full type system details, see the [Type System reference][type-system]. -Help me ChatGPT, you're my only hope - -## Summary +## Design Structure /// admonition | Module Definition type: verilog @@ -62,32 +60,41 @@ parameter [7:0] p = 8’b1011; ```scala linenums="0" title="DFHDL" val p: Bits[8] <> CONST = b"8'1011" ``` + + +Inter-dependent parameters and ports example: + +
```sv linenums="0" title="Verilog" -module Concat #( - parameter int len1; - parameter int len2; - localparam int outlen = len1 + len2 -) ( - input [len1-1:0] i1; - input [len2-1:0] i2; - output [outlen-1:0] o +module Concat#( + parameter int len1 = 8, + parameter int len2 = 8, + parameter logic [7:0] midVec = 8'h55 +)( + input wire logic [len1 - 1:0] i1, + input wire logic [len2 - 1:0] i2, + output logic [outlen - 1:0] o ); - assign o = {i1, i2}; + localparam int midLen = 8; + localparam int outlen = len1 + midLen + len2; + assign o = {i1, midVec, i2}; endmodule ``` ```scala linenums="0" title="DFHDL" class Concat( - val len1: Int <> CONST - val len2: Int <> CONST + val len1: Int <> CONST = 8, + val len2: Int <> CONST = 8, + val midVec: Bits[Int] <> CONST = h"55" ) extends EDDesign: - val outlen = len1 + len2 - val i1 = Bits(len1) <> IN - val i2 = Bits(len2) <> IN - val o = Bits(outlen) <> OUT - - o <> (i1, i2) + val midLen = midVec.width + val outlen = len1 + midLen + len2 + val i1 = Bits(len1) <> IN + val i2 = Bits(len2) <> IN + val o = Bits(outlen) <> OUT + + o <> (i1, midVec, i2) end Concat ``` @@ -105,9 +112,591 @@ reg [7:0] v = 8’b1011; ``` ```scala linenums="0" title="DFHDL" -val v = Bits(8) <> VAR init b"8'1011" +val v = Bits(8) <> VAR init b"8’1011" +``` + +
+/// + +## Types and Literals + +/// admonition | Numeric Literals + type: verilog +DFHDL uses string interpolators for sized literals. Each type has its own interpolator -- do not mix Verilog base prefixes (`’b`, `’d`, `’h`) inside them. + +
+ +```sv linenums="0" title="Verilog" +8’b1011_0000 // binary +8’hB0 // hex +8’d176 // decimal +5’d27 // 5-bit decimal +``` + +```scala linenums="0" title="DFHDL" +b"8’1011_0000" // binary (b"...") +h"8’B0" // hex (h"...") +d"8’176" // unsigned decimal (d"...") +d"5’27" // 5-bit unsigned decimal +``` + +
+ +`b"..."` accepts only binary digits (`0`, `1`, `?`). Writing `b"5’d27"` is an error -- use `d"5’27"` for decimal values. +/// + +/// admonition | `$clog2` and Width Computation + type: verilog +Instead of computing widths manually with `$clog2`, use `.until` or `.to` constructors which set the width automatically: + +| Verilog | DFHDL | Width | +|---------|-------|-------| +| `$clog2(N)` | `UInt.until(N)` / `Bits.until(N)` | `clog2(N)` bits, valid for N >= 2 | +| `$clog2(N+1)` | `UInt.to(N)` / `Bits.to(N)` | `clog2(N+1)` bits, valid for N >= 1 | + +`UInt.until(1)` is **invalid** (would produce 0-bit width). For counters that count 0 to N inclusive (common with `$clog2(N+1)`), use `UInt.to(N)`. + +
+ +```sv linenums="0" title="Verilog" +parameter RATE = 5208; +localparam WIDTH = $clog2(RATE); +reg [WIDTH-1:0] counter; + +// Counter 0..SCALE inclusive +reg [$clog2(SCALE+1)-1:0] cnt = 0; +``` + +```scala linenums="0" title="DFHDL" +val RATE: Int <> CONST = 5208 +val counter = UInt.until(RATE) <> VAR + + +// Counter 0..SCALE inclusive +val cnt = UInt.to(SCALE) <> VAR init 0 +``` + +
+ +/// admonition | Choosing `.until(N)` vs `.to(N)` for counters + type: warning +If the Verilog counter resets *when it reaches* `N` (i.e., `counter == N`), the variable must be able to hold the value `N`, so use `UInt.to(N)`. If the counter only ever holds values `0..N-1`, use `UInt.until(N)`. For many values of `N`, both produce the same bit width (`clog2(N) == clog2(N+1)`), so the bug is silent until formal verification catches an out-of-range comparison. +/// + +See [UInt/SInt constructors][DFDecimal] and [Bits constructors][DFBits] for details. See also the [`clog2` anti-pattern warning][int-param-ops]. +/// + +## Processes and Sequential Logic + +/// admonition | Process Blocks (always) + type: verilog +Verilog `always` blocks map to DFHDL ED domain `process(...)` blocks. + +
+ +```sv linenums="0" title="Verilog" +// Combinational +always @(*) begin + y = a + b; +end + +// Sequential (clocked) +always @(posedge clk) begin + counter <= counter + 1; +end + +// Sequential with async reset +always @(posedge clk or posedge rst) + if (rst) + q <= 0; + else + q <= d; +``` + +```scala linenums="0" title="DFHDL" +// Combinational +process(all): + y := a + b + + +// Sequential (clocked) +process(clk): + if (clk.rising) + counter :== counter + 1 + +// Sequential with async reset +process(clk, rst): + if (rst) + q :== 0 + else if (clk.rising) + q :== d +``` + +
+ +- `always @(*)` becomes `process(all):` +- `always @(posedge clk)` becomes `process(clk):` with `if (clk.rising)` inside +- Verilog blocking `=` becomes DFHDL `:=` (use in combinational processes) +- Verilog non-blocking `<=` becomes DFHDL `:==` (use in clocked processes) +/// + +/// admonition | Variable Initialization (init) + type: verilog +In ED domain, `init` on a `VAR` generates a Verilog `reg` with an initial value. This maps to both `initial begin` blocks and `reg ... = value` declarations. + +
+ +```sv linenums="0" title="Verilog" +reg [7:0] counter = 8’d0; +// or equivalently: +// initial counter = 8’d0; +``` + +```scala linenums="0" title="DFHDL" +val counter = UInt(8) <> VAR init 0 +``` + +
+ +An `output reg` with an initial value maps directly to `OUT init`: + +
+ +```sv linenums="0" title="Verilog" +module Foo( + input clk, + input din, + output reg dout +); + initial dout = 1'b1; + always @(posedge clk) + dout <= din; +endmodule +``` + +```scala linenums="0" title="DFHDL" +@top class Foo extends EDDesign: + val clk = Bit <> IN + val din = Bit <> IN + val dout = Bit <> OUT init 1 + process(clk): + if (clk.rising) + dout :== din +``` + +
+/// + +/// admonition | FSM State Encoding + type: verilog +Verilog FSMs typically use `parameter` constants and `case`/`if` chains. In DFHDL, the idiomatic translation uses an `enum extends Encoded` and `match`: + +
+ +```sv linenums="0" title="Verilog" +parameter READY = 2'b00, + AIM = 2'b01, + FIRE = 2'b10; +reg [1:0] state = READY; + +always @(posedge clk) + case (state) + READY: if (go) state <= AIM; + AIM: state <= FIRE; + FIRE: state <= READY; + default: state <= READY; + endcase +``` + +```scala linenums="0" title="DFHDL" +enum State extends Encoded: + case Ready, Aim, Fire +import State.* +val state = State <> VAR init Ready + +process(clk): + if (clk.rising) + state match + case Ready => if (go) state :== Aim + case Aim => state :== Fire + case Fire => state :== Ready + case _ => state :== Ready +``` + +
+ +If the encoded Verilog state values follow a standard pattern (incremental, gray, one-hot), use the corresponding `Encoded` variant. For non-standard encodings, use `Encoded.Manual` with a constructor parameter: + +```scala +// Verilog: parameter INIT=3'b000, RUN=3'b011, DONE=3'b101, ERR=3'b110; +enum Phase(val value: UInt[3] <> CONST) extends Encoded.Manual(3): + case Init extends Phase(0) + case Run extends Phase(3) + case Done extends Phase(5) + case Err extends Phase(6) +``` + +The constructor parameter `(val value: UInt[N] <> CONST)` is required for `Encoded.Manual` and the bit width `N` must match the argument to `Encoded.Manual(N)`. Omitting it causes a compile error. See [Enumeration][DFEnum] for all encoding options. + +When the Verilog FSM uses an explicit reset instead of `initial`, omit the `init` and handle reset inside the clocked process: + +
+ +```sv linenums="0" title="Verilog" +always @(posedge clk) + if (rst) + state <= READY; + else + case (state) + READY: if (go) state <= AIM; + AIM: state <= FIRE; + FIRE: state <= READY; + default: state <= READY; + endcase +``` + +```scala linenums="0" title="DFHDL" +val state = State <> VAR // no init + +process(clk): + if (clk.rising) + if (rst) + state :== Ready + else + state match + case Ready => if (go) state :== Aim + case Aim => state :== Fire + case Fire => state :== Ready + case _ => state :== Ready +``` + +
+ +Avoid modeling FSM states as `Bits` or `UInt` constants -- it is an anti-pattern. When compiling to SystemVerilog (SV), the SV enums are being utilized as well. +/// + +/// admonition | Integer `case` Statements (non-enum) + type: verilog +When the Verilog `case` selector is a plain integer counter (not an FSM), use `match` with integer literal cases directly: + +
+ +```sv linenums="0" title="Verilog" +case (sel) + 'd0: out <= 60; + 'd1: out <= 110; + 'd2 | 'd3: out <= 200; + default: out <= 0; +endcase +``` + +```scala linenums="0" title="DFHDL" +sel match + case 0 => out :== 60 + case 1 => out :== 110 + case 2 | 3 => out :== 200 + case _ => out :== 0 +end match +``` + +
+ +Integer literal pattern matching works with `UInt` and `SInt` selectors. Guard conditions (`case _ if sel == N`) also work but are less idiomatic. See [Match Expressions][match-expressions] for full details. +/// + +## Operations + +/// admonition | Shift Operators + type: verilog +Verilog has separate `>>` (logical) and `>>>` (arithmetic) right shift operators. DFHDL uses only `>>`, but the behavior depends on the operand type: + +
+ +```sv linenums="0" title="Verilog" +module RightShifter( + input wire logic [7:0] data, + output logic [7:0] logical, + output logic [7:0] arith +); + assign logical = data >> 1; + assign arith = data >>> 1; +endmodule +``` + +```scala linenums="0" title="DFHDL" +class RightShifter extends EDDesign: + val data = Bits(8) <> IN + val logical = Bits(8) <> OUT + val arith = Bits(8) <> OUT + + logical <> data >> 1 + arith <> (data.sint >> 1).bits +end RightShifter +``` + +
+ +There is no `>>>` operator in DFHDL. The type of the LHS determines the shift semantics: `>>` on `UInt`/`Bits` zero-fills, `>>` on `SInt` sign-extends. See [Shift Operations][shift-ops] for details. +/// + + +/// admonition | Bit/Boolean Operators: `|`/`&` and `||`/`&&` + type: verilog +In DFHDL, `||`/`&&` and `|`/`&` are interchangeable on `Bit` and `Boolean` types. The generated Verilog operator depends on the LHS type: `Bit` produces bitwise `|`/`&`, `Boolean` produces logical `||`/`&&`. +
+ +```sv linenums="0" title="Verilog" +input a, b, c; +output o1, o2, o3; +assign o1 = a | b | c; +assign o2 = a | b | c; +assign o3 = a || b || c; +``` + +```scala linenums="0" title="DFHDL" +val a, b, c = Bit <> IN +val o1, o2, o3 = Bit <> OUT +o1 <> a | b | c +o2 <> a || b || c +o3 <> a.bool || b || c +``` + +
+ +See [Logical Operations][logical-ops] for the full reference and Verilog/VHDL mapping tables. +/// + +/// admonition | Reduction Operators (`&v`, `|v`, `^v`) + type: verilog +Verilog's unary reduction operators have direct DFHDL equivalents using postfix `.&`, `.|`, `.^` on `Bits` values: + +
+ +```sv linenums="0" title="Verilog" +logic [7:0] v; +logic all_set = &v; // AND reduce +logic any_set = |v; // OR reduce +logic parity = ^v; // XOR reduce +logic not_all = ~&v; // NAND reduce +logic none_set = ~|v; // NOR reduce +``` + +```scala linenums="0" title="DFHDL" +val v = Bits(8) <> VAR +val all_set = v.& // Bit: AND reduce +val any_set = v.| // Bit: OR reduce +val parity = v.^ // Bit: XOR reduce +val not_all = !v.& // Bit: NAND reduce +val none_set = !v.| // Bit: NOR reduce +``` + +
+ +See [Bit Reduction Operations][reduction-ops] for full details. +/// + +/// admonition | Part-Select Notation (`-:` and `+:`) + type: verilog +Verilog's descending and ascending part-select notation maps to DFHDL's `(hi, lo)` range slice, or use the convenience methods `.msbits(n)` and `.lsbits(n)`: + +| Verilog | DFHDL | Notes | +|---------|-------|-------| +| `sig[base -: W]` | `sig(base, base - W + 1)` | Descending slice from `base`, `W` bits | +| `sig[base +: W]` | `sig(base + W - 1, base)` | Ascending slice from `base`, `W` bits | +| `sig[N-1 -: W]` | `sig.msbits(W)` or `sig(N-1, N-W)` | Top `W` bits | +| `sig[0 +: W]` | `sig.lsbits(W)` or `sig(W-1, 0)` | Bottom `W` bits | +| `sig[idx]` | `sig(idx)` | Single bit access | + +
+ +```sv linenums="0" title="Verilog" +logic [15:0] data; +logic [3:0] top4 = data[15 -: 4]; +logic [3:0] bot4 = data[0 +: 4]; +logic bit5 = data[5]; +``` + +```scala linenums="0" title="DFHDL" +val data = Bits(16) <> VAR +val top4 = data.msbits(4) // top 4 bits +val bot4 = data.lsbits(4) // bottom 4 bits +val bit5 = data(5) // single bit +``` + +
+/// + +/// admonition | Arithmetic with Signed Values and Constants + type: verilog +DFHDL arithmetic requires the LHS to be at least as wide as the RHS and to have compatible sign. A plain Scala `Int` literal is unsigned, so it cannot appear on the LHS of arithmetic with `SInt`. The best practice is to use **sized signed literals** (`sd"W'value"`) with the target operation width: + +```scala +// Verilog: y <= 2 * mul_val + y0; (all signed, 16-bit) +// ERROR: plain 2 is unsigned +y :== 2 * mul_val + y0 +// CORRECT: use a sized signed literal +y :== sd"16'2" * mul_val + y0 + +// Verilog: err <= 2 - (2 * r0); (signed, 18-bit result) +// ERROR: sd"2" is SInt[3], narrower than RHS +err :== sd"2" - (r0.resize(CORDW + 2) * 2) +// CORRECT: match the LHS width to the operation width +err :== sd"${CORDW + 2}'2" - (r0.resize(CORDW + 2) * 2) +``` + +The general rule: the **wider** operand must be on the LHS, and when mixing constants with signed DFHDL values, use `sd"W'value"` with the appropriate width `W`. + +See [Arithmetic Operations][arithmetic-ops] and [Carry Arithmetic][carry-ops] for full details. +/// + +## Parametric Constants + +/// admonition | Parametric-Width Bits Constants + type: verilog +Verilog parameters can be bit-vector constants whose width depends on another parameter. In DFHDL, use `Bits[Int] <> CONST` (unbounded width) for the parameter. The width is inferred from the default literal value, and other local values can derive their widths from it: + +
+ +```sv linenums="0" title="Verilog" +module MyDesign #( + parameter WIDTH = 8, + parameter [WIDTH-1:0] MASK = 8'hB8 +)( + output logic [WIDTH-1:0] data +); + // ... +endmodule +``` + +```scala linenums="0" title="DFHDL" +class MyDesign( + val WIDTH: Int <> CONST = 8, + val MASK: Bits[Int] <> CONST = h"B8" +) extends EDDesign: + // MASK.width gives the actual width + val data = Bits(MASK.width) <> OUT + // ... +``` + +
+ +See the [Parameter Declarations](#parameter-declarations) section above for a complete inter-dependent parameters example. +/// + +## Common Pitfalls + +/// admonition | Scala Reserved Keywords as DFHDL Port or Variable Names + type: verilog +Some Verilog port names (`val`, `type`, `class`, `match`, `case`, `object`, etc.) are reserved in Scala. Use backtick escaping: + +
+ +```sv linenums="0" title="Verilog" +module foo( + output reg signed [15:0] val +); + assign val = 16'sd42; +``` + +```scala linenums="0" title="DFHDL" +class foo extends EDDesign: + val `val` = SInt(16) <> OUT + `val` <> 42 +``` + +
+ +Alternatively, use a non-keyword name with the Scala `@targetName` annotation to set the actual HDL name: + +
+ +```sv linenums="0" title="Verilog" +module foo( + output logic signed [15:0] class +); + assign class = 16'sd42; +endmodule +``` + +```scala linenums="0" title="DFHDL" +import scala.annotation.targetName +class foo extends EDDesign: + @targetName("class") + val class_ = SInt(16) <> OUT + class_ <> 42 +``` + +
+ + +/// + +/// admonition | `Bits` Initialization or Assignment + type: verilog +`Bits` values cannot be initialized or assigned with plain integers. Use `all(0)` or a sized literal: + +
+ +```sv linenums="0" title="Verilog" +reg [7:0] flags = 8'd0; +reg [7:0] mask = 8'hFF; +``` + +```scala linenums="0" title="DFHDL" +val flags = Bits(8) <> VAR init all(0) +val mask = Bits(8) <> VAR init h"8'FF" +// NOT: Bits(8) <> VAR init 0 // error +``` + +
+ +Note: `UInt` and `SInt` can be initialized or assigned with plain integers. +/// + +/// admonition | Inline Conditional Expressions + type: verilog +Verilog's ternary operator `cond ? a : b` has three DFHDL equivalents: + +
+ +```sv linenums="0" title="Verilog" +assign out = cond ? a : b; + +always @(*) + out = cond ? a : b; +``` + +```scala linenums="0" title="DFHDL" +// 1. Using .sel (closest to ternary) +out <> cond.sel(a, b) + +// 2. Inline if/else (wrap in parentheses) +out <> (if (cond) a else b) + +// 3. Statement form +if (cond) out := a +else out := b ```
+ +The `.sel` method compiles directly to Verilog's ternary operator. For complex nested conditions, prefer `if`/`else` or `match` over chaining `.sel` calls. See [Selection (.sel)][sel-ops] for details. + +When using inline `if`/`else` as the RHS of `:=` or `:==`, **parentheses are required**. Without them, Scala 3 parses the `if` as a statement, not an expression: + +```scala +// CORRECT: parenthesized inline if with := and <> +out := (if (cond) a else b) +out <> (if (cond) a else b) + +// PARSE ERROR: bare inline if on RHS of := or <> +// out := if (cond) a else b // "end of statement expected" + +// CORRECT: statement form (no parentheses needed) +if (cond) out := a +else out := b +``` + +This applies to all assignment operators (`:=`, `:==`, `<>`). Use `.sel` or the parenthesized form for inline conditionals; use the statement form for multi-assignment branches. /// diff --git a/docs/user-guide/conditionals/index.md b/docs/user-guide/conditionals/index.md index 86c6f9840..7da6797de 100644 --- a/docs/user-guide/conditionals/index.md +++ b/docs/user-guide/conditionals/index.md @@ -184,6 +184,30 @@ else - Optimizes bit pattern matching into efficient comparisons - Extracts struct fields into temporary variables when needed +/// admonition | Use enumerations for state matching + type: tip +For FSM-style `match` over states, define an enumeration with the appropriate [encoding](../type-system/index.md#dfhdl-enumeration-enum--extends-encoded) and match on its members: + +```scala +enum State extends Encoded: // 00, 01, 10 + case Idle, Start, Data + +val state = State <> VAR +state match + case State.Idle => // idle logic + case State.Start => // start logic + case State.Data => // data logic +``` + +For non-sequential state encodings, use `Encoded.Manual`: +```scala +enum State(val value: UInt[3] <> CONST) extends Encoded.Manual(3): + case Idle extends State(d"3'0") + case Start extends State(d"3'3") + case Data extends State(d"3'5") +``` +/// + ### Best Practices 1. **Use Match for Multi-Way Branching**: When dealing with multiple cases, match is often clearer than nested if-else diff --git a/docs/user-guide/processes/index.md b/docs/user-guide/processes/index.md index eba028ee2..b1ccf6321 100644 --- a/docs/user-guide/processes/index.md +++ b/docs/user-guide/processes/index.md @@ -1 +1,229 @@ -# Processes \ No newline at end of file +# Processes + +Processes define *when* a block of logic runs. DFHDL supports processes in two domains: + +- **RT domain**: A **clock-bound** process used to describe finite-state machines (FSMs). The process runs in lockstep with the domain clock and uses steps, waits, and control flow that the compiler lowers to registers and combinational logic. +- **ED domain**: **Sensitivity-driven** processes that run when listed signals change (or all read signals with `process(all)`), giving the same level of control as `process` in VHDL or `always` in Verilog. + +Processes are not available in the dataflow (DF) domain. Processes cannot be nested inside another process. + +## RT domain: clock-bound FSM process + +In an [RT design][design-domains], a process is used to describe a finite-state machine that is **clock-bound**: it advances on the domain clock and is compiled to a state register plus combinational next-state and output logic. + +### Syntax: `process` / `process.forever` + +Use the shorthand `process:` (or `process.forever`) inside an `RTDesign` or `RTDomain`. The block contains either plain combinational logic (assignments, no steps) or step definitions that form an FSM. + +### Step-based FSM + +Define states as `def Name: Step = ...` and control flow with: + +- **`NextStep`** — advance to the next step in definition order. +- **`ThisStep`** — stay in the current step for another cycle. +- **`FirstStep`** — go to the first step (e.g. reset to initial state). +- **Step name** (e.g. `S1`, `S2`) — jump to that step. + +You can optionally name the process (e.g. `val my_fsm = process:`) so the compiler uses that name for the generated state enum and state register. + +```scala +class SimpleFSM extends RTDesign: + val x = Bit <> IN + val y = Bit <> OUT.REG init 0 + + process: + def S0: Step = + y.din := 0 + if (x) NextStep else S0 + def S1: Step = + y.din := 1 + if (x) S2 else FirstStep + def S2: Step = + y.din := 0 + if (x) ThisStep else FirstStep +``` + +The compiler lowers this to a state enum, a state register, and a `match` on the state; see [Design Domains][design-domains] for the compilation flow. + +### fallThrough + +A step can define **`def fallThrough = cond`** where `cond` is a Boolean/Bit expression. When the condition holds, the step advances to the next step in the same cycle (conditional advancement); when it does not, the FSM stays in the current step. + +### onEntry and onExit + +Inside a step you can define: + +- **`def onEntry = ...`** — run when entering the step (once per transition into this state). +- **`def onExit = ...`** — run when leaving the step (once per transition out of this state). + +```scala +def S1: Step = + def onEntry = + y.din := 1 + if (x) S2 else FirstStep +def S2: Step = + def onExit = + y.din := 0 + if (x) ThisStep else FirstStep +``` + +### Waits and loops + +RT processes can use **cycle waits** (e.g. `1.cy.wait`, `n.cy.wait`) and **wait conditions** (`waitUntil(cond)`, `waitWhile(cond)`). The compiler converts these into step blocks and counters so that the behavior remains clock-bound and synthesizable. + +### Process with no steps + +If the process body has no step definitions, it is purely combinational and runs every cycle: + +```scala +process: + y.din := x +``` + +## ED domain: sensitivity-driven processes + +In an [ED design][design-domains], processes are **sensitivity-driven**: they run when an event occurs on one of their sensitivity signals (or on any read signal with `process(all)`). + +## ED process forms + +### Sensitivity list: `process(sig1, sig2, ...)` + +The process runs whenever any of the listed signals change. + +```scala +class CombAndSeq extends EDDesign: + val clk = Bit <> IN + val rst = Bit <> IN + val x = UInt(8) <> IN + val y = UInt(8) <> OUT + + // Combinational logic: runs when x changes + process(x): + y := x + 1 + + // Sequential logic: runs on clock (and optionally reset) events + val r = UInt(8) <> VAR init 0 + process(clk): + if (clk.rising) + r :== x +``` + +You can list multiple signals, including edge-qualified signals (see [Edge sensitivity](#edge-sensitivity)). + +### Combinational-style: `process(all)` + +The process is sensitive to *all* signals that are read in the block. Use this for combinational logic that should react to any input change. The compiler infers the actual sensitivity list from the block body. + +```scala +class CombLogic extends EDDesign: + val a = UInt(8) <> IN + val b = UInt(8) <> IN + val y = UInt(8) <> OUT + + process(all): + y := a + b +``` + +### Forever process: `process.forever` / `process` + +A process with no sensitivity list runs continuously. It is allowed in RT and ED, but **not** in DF. The shorthand `process:` (no arguments) is rewritten by the compiler to `process.forever`. + +- **In RT**: `process:` is the [clock-bound FSM process](#rt-domain-clock-bound-fsm-process) described above (steps, waits, etc.). +- **In ED**: Use it for testbenches or clock generation (e.g. toggling a clock with `wait`). + +```scala +class Testbench extends EDDesign: + val clk = Bit <> VAR + process.forever: + clk := !clk + 5.ns.wait +``` + +## Edge sensitivity + +For sequential (clocked) logic you typically want the process to run only on a specific clock edge. You can either: + +1. **List the clock and check the edge inside the block** (VHDL-style): + +```scala +process(clk): + if (clk.rising) + reg :== nextVal +``` + +2. **Put the edge in the sensitivity list** (Verilog-style; compiler may normalize to this): + +```scala +process(clk.rising): + reg :== nextVal +``` + +Edge options are `.rising` and `.falling` on clock (or bit) signals. When reset is used, list both clock and reset and branch on reset then clock edge: + +```scala +process(clk, rst): + if (rst) + out :== 0 + else if (clk.rising) + out :== nextVal +``` + +## Assignments inside processes + +### Blocking assignment `:=` + +Takes effect immediately within the process. Use for combinational logic and for intermediate values that are read later in the same process. + +```scala +process(all): + val temp = a + b // read a, b + y := temp // immediate update of y +``` + +### Non-blocking assignment `:==` + +Schedules an update at the end of the current evaluation step. Use for registers and outputs that should not create combinational feedback within the same process. + +```scala +process(clk): + if (clk.rising) + counter :== counter + 1 // register update +``` + +/// admonition | Rule of thumb + type: tip +Use `:=` for combinational (e.g. in `process(all)` or combinational branches). Use `:==` for register and sequential outputs in clocked processes. +/// + +## Local variables + +You can declare local variables inside a process with `VAL` or `VAR`; they are visible only within that process and help structure combinational or sequential logic. + +```scala +process(all): + val z = UInt(8) <> VAR + if (x > 10) + z := x + 1 + else + z := x - 1 + y := z +``` + +## Relation to design domains + +| Domain | Processes | +|--------|-----------| +| **DF** | No processes. Behavior is expressed with dataflow and `.prev`; the compiler introduces registers and eventually ED processes. | +| **RT** | **Clock-bound FSM process**: `process:` (or `process.forever`) with optional step definitions (`def Name: Step = ...`), `onEntry`/`onExit`, and waits. Compiled to a state register and match logic. Plain RT register code (no process) is also lowered to ED processes by the compiler. | +| **ED** | **Sensitivity-driven**: `process(sig1, sig2, ...)`, `process(all)`, and `process.forever` / `process`. Full control over sensitivity and blocking vs non-blocking assignment. | + +See [Design Domains][design-domains] for the overall flow from DF → RT → ED and how processes fit into compilation. + +## Summary + +- **RT**: Use **`process:`** in **RTDesign** / **RTDomain** for a clock-bound FSM with **`def Name: Step = ...`**, **`NextStep`** / **`ThisStep`** / **`FirstStep`**, and optional **`onEntry`** / **`onExit`** and waits. +- **ED**: Use **`process(sig1, sig2, ...)`** or **`process(all)`** in **EDDesign** / **EDDomain** to define when a block runs; **`process(all)`** for combinational logic; **`process(clk)`** (and optionally **`process(clk, rst)`**) with **`clk.rising`** / **`clk.falling`** for sequential logic. +- Use **`:=`** for immediate (blocking) updates and **`:==`** for register (non-blocking) updates in ED processes. +- Processes cannot be nested and are not available in the DF domain. + +[design-domains]: ../design-domains/index.md diff --git a/docs/user-guide/type-system/index.md b/docs/user-guide/type-system/index.md index 75aac1062..8d766c44b 100755 --- a/docs/user-guide/type-system/index.md +++ b/docs/user-guide/type-system/index.md @@ -445,73 +445,6 @@ class Foo extends DFDesign: /// -## Bit-Accurate Operations and Type Inference - -DFHDL provides bit-accurate operations and strong type inference for bit-level manipulations. Here are the key features: - -### Bit Selection and Slicing -```scala -val b8 = Bits(8) <> VAR -// Most significant bits selection -val ms7 = b8(7, 1) // 7 MSBs -val ms1 = b8(7, 7) // MSB only - -// Least significant bits selection -val ls7 = b8(6, 0) // 7 LSBs -val ls1 = b8(0, 0) // LSB only - -// Single bit access -val msbit = b8(7) // MSB -val lsbit = b8(0) // LSB -``` - -### Bit Operations -```scala -val b8 = Bits(8) <> VAR - -// Shift operations -val shifted_left = b8 << 2 // Logical left shift -val shifted_right = b8 >> 2 // Logical right shift - -// Bit reduction operations -val or_reduced = b8.| // OR reduction -val and_reduced = b8.& // AND reduction -val xor_reduced = b8.^ // XOR reduction - -// Bit concatenation -val concat = (b"100", b"1", b"0", b"11").toBits // Creates 8-bit value -``` - -### Multiple Variable Assignment -```scala -val b4M, b4L = Bits(4) <> VAR // Declare multiple variables -val b3M = Bits(3) <> VAR -val u5L = UInt(5) <> VAR - -// Assign to multiple variables using tuple pattern -(b4M, b4L) := (h"1", 1, 0, b"11") // Values are concatenated and split - -// Mix different types in assignment -(b3M, u5L) := (h"1", 1, 0, b"11") // Values automatically cast to appropriate types - -// Assign bit slices to multiple variables -(b4M, b4L) := (u8.bits(3, 0), u8.bits(7, 4)) // Split byte into nibbles - -// Complex multiple assignment -(b4M, b3M, u5L, b4L) := (u8, b8) // Automatically extracts appropriate bits for each variable -``` - -### Width Inference and Resizing -```scala -// Automatic width inference -val b3 = Bits(3) <> VAR -val b8 = Bits(8) <> VAR - -// Explicit resizing required when widths don't match -b8 := b3.resize(8) // Zero-extend to 8 bits -b3 := b8.resize(3) // Truncate to 3 bits -``` - ## Bubble Values {#bubble} * RT and ED - Don't Care / Unknown @@ -584,7 +517,111 @@ val x = b8 ++ h"FF" //ok val y = b8 ++ all(0) //error ``` -## `Bit`/`Boolean` DFHDL Values {#DFBitOrBool} +## Type Signatures and Parameterization {#type-sigs} + +Every DFHDL value has a type of the form `T <> M`, where `T` is the DFHDL type (shape) and `M` is the modifier that determines how the value can be used. + +### Modifier Categories + +Modifiers fall into two groups: + +**Declaration modifiers** — used in `val` declarations with the `<>` operator: + +- `VAR`, `VAR.REG`, `VAR.SHARED` — variables +- `IN`, `OUT`, `OUT.REG`, `INOUT` — ports + +**Type signature modifiers** — used in type annotations for parameters, struct fields, and method signatures: + +- `CONST` — compile-time or elaboration-time constant parameter +- `VAL` — read-only value (struct fields, method parameters) +- `DFRET` / `RTRET` / `EDRET` — method return types (DF, RT, or ED domain) + +### Design Parameters + +Design classes accept parameters as constructor arguments using `<> CONST`: + +```scala +class Counter(val width: Int <> CONST = 8) extends RTDesign: + val cnt = UInt(width) <> OUT.REG init 0 +``` + +- `Int <> CONST` for integer parameters (used for widths, lengths, counts) +- Typed constants like `Bits[8] <> CONST` and `UInt[8] <> CONST` are also possible +- Default values are optional + +### `VAL` Modifier + +`VAL` marks a read-only value. It is used for: + +- **Struct field declarations:** + ```scala + case class Point(x: UInt[8] <> VAL, y: UInt[8] <> VAL) extends Struct + ``` +- **Method/design-def parameters:** + ```scala + def increment(x: UInt[8] <> VAL): UInt[8] <> DFRET = x + 1 + ``` + +`VAL` values cannot be assigned or connected — they are inputs to the computation. + +### Design Defs and `DFRET` + +Design defs are functional helpers. Arguments use `<> VAL`, return types use `<> DFRET` (or `RTRET`/`EDRET` for domain-specific defs): + +```scala +// DF domain design def +def double(value: Bits[Int] <> VAL): Bits[Int] <> DFRET = (value, value) + +// Opaque type extension method +extension (c: Counter <> VAL) + def increment: Counter <> DFRET = (c.actual + 1).as(Counter) +``` + +### Bounded and Unbounded Types {#bounded-unbounded} + +DFHDL types carry their size (width or length) as a Scala type parameter. There are three levels of size specificity: + +**Bounded** — the size is a literal singleton known at compile time. All type checks happen statically: + +```scala +val a: UInt[8] <> CONST = d"255" +val b: Bits[4] <> CONST = h"A" +val v: Bits[8] X 4 <> CONST = all(all(0)) +``` + +**Parameterized bounded** — the size is the singleton type of a named parameter. The compiler can track the relationship, even though the concrete value isn't known until instantiation: + +```scala +class Foo(val w: Int <> CONST) extends RTDesign: + val x: Bits[w.type] <> CONST = all(0) // width tied to parameter w + val y = UInt[w.type] <> VAR init 0 // same + val v: UInt[4] X w.type <> CONST = all(0) // vector length tied to w +``` + +**Unbounded** — the size is bare `Int`, with no compile-time size information. Used when the type is too complex to express at the Scala type level (e.g., results of operations on parameterized types). The DFHDL compiler still has the required size information available during elaboration, where it is checked: + +```scala +val cu: UInt[Int] <> VAL = 1 +val cs: SInt[Int] <> VAL = -1 +val bv: Bits[8] X Int <> CONST = Vector(h"12", h"34") +def twice(value: Bits[Int] <> VAL): Bits[Int] <> DFRET = (value, value) +``` + +/// admonition | Struct fields must be bounded + type: warning +Struct field types cannot be unbounded. Each field must have a concrete or parameterized-bounded type: +```scala +// CORRECT: bounded fields +case class Pkt(header: Bits[8] <> VAL, data: UInt[32] <> VAL) extends Struct + +// ERROR: unbounded fields are not allowed +// case class Bad(data: Bits[Int] <> VAL) extends Struct +``` +/// + +## DFHDL Value Types + +### `Bit`/`Boolean` {#DFBitOrBool} `Bit` DFHDL values represent binary `1` or `0` values, whereas `Boolean` DFHDL values represent `true` or `false` values, respectively. The `Bit` and `Boolean` DFHDL values are generally interchangeable, and automatically converted between one and the other. @@ -594,11 +631,11 @@ Although they are interchangeable, it's generally recommended to use `Boolean` D /// /// details | Why have both `Bit` and `Boolean` DFTypes? - type: note + type: note The main reason to differentiate between `Bit` and `Boolean` is that VHDL has both `std_logic` and `boolean` types, respectively. Verilog has only a single `logic` or `wire` to represent both. Indeed VHDL'2008 has relaxed some of the type constraints, but not enough. And nevertheless, DFHDL aims to support various HDL dialects, and thus enables simple implicit or explicit conversion between these two DFType values. /// -### DFType Constructors +#### DFType Constructors Use the `Bit` or `Boolean` objects/types to construct `Bit` or `Boolean` DFHDL values, respectively. @@ -609,7 +646,10 @@ val c_bit: Bit <> CONST = 1 val c_bool: Boolean <> CONST = false ``` -### Candidates +#### Type Signatures +`Bit` and `Boolean` have no size parameter. Type signatures: `Bit <> CONST`, `Bit <> VAL`, `Boolean <> VAL`, etc. + +#### Candidates * DFHDL `Bit` values. * DFHDL `Boolean` values. @@ -648,209 +688,31 @@ val TrueVal: Boolean = 1 bool := TrueVal ``` -### Operations - -#### Explicit Casting Operations - -These operations propagate constant modifiers, meaning that if the casted argument is a constant, the returned value is also a constant. - -/// html | div.operations -| Operation | Description | LHS Constraints | Returns | -| ----------- | --------------------------------|-----------------------|-----------------------| -| `lhs.bool` | Cast to a DFHDL `Boolean` value | `Bit` DFHDL value | `Boolean` DFHDL value | -| `lhs.bit` | Cast to a DFHDL `Bit` value | `Boolean` DFHDL value | `Bit` DFHDL value | -/// - -```scala -val bt1 = Bit <> VAR -val bl1 = bt1.bool -val bl2 = Boolean <> VAR -val bt2 = bl2.bit -val bt3: Bit <> CONST = 0 -val bl3: Boolean <> CONST = bt3.bool -val bl4: Boolean <> CONST = true -val bt4: Bit <> CONST = bt4.bit -// error: bt1 is not a constant -val err: Bit <> CONST = bt1 -``` - -#### Bit History Operations - -Currently these operations are only supported under ED domains. However, in upcoming DFHDL updates, support will be added across all domain abstractions. - -/// html | div.operations -| Operation | Description | LHS Constraints | Returns | -| ------------ | --------------------------------|-----------------------|-----------------------| -| `lhs.rising` | True when a value changes from `0` to `1` | `Bit` DFHDL value | `Boolean` DFHDL value | -| `lhs.falling` | True when a value changes from `1` to `0` | `Bit` DFHDL value | `Boolean` DFHDL value | -/// - -```scala -class Foo extends EDDesign: - val clk = Bit <> IN - - /* VHDL-style */ - process(clk): - if (clk.rising) - //some sequential logic - - /* Verilog-style */ - process(clk.rising): - //some sequential logic -``` - -/// details | Transitioning from Verilog - type: verilog -Under the ED domain, the `x.rising` and `x.falling` operations are equivalent to the Verilog `posedge x` and `negedge x`, respectively. -In future releases these operations will have an expanded functionality under the other design domains. -/// - -/// details | Transitioning from VHDL - type: vhdl -Under the ED domain, the `x.rising` and `x.falling` operations are equivalent to the VHDL `rising_edge(x)` and `falling_edge(x)`, respectively. -In future releases these operations will have an expanded functionality under the other design domains. -/// - -For more information see either the [design domains][design-domains] or [processes][processes] sections. - -#### Logical Operations - -Logical operations' return type always match the LHS argument's type. -These operations propagate constant modifiers, meaning that if all arguments are constant, the returned value is also a constant. - -/// html | div.operations -| Operation | Description | LHS/RHS Constraints | Returns | -| ------------ | ----------- | ------------------- | ------- | -| `lhs && rhs` | Logical AND | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | -| `lhs || rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | -| `lhs ^ rhs` | Logical XOR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | -| `!lhs` | Logical NOT | The argument must be a `Bit`/`Boolean` DFHDL value. | LHS-Type DFHDL value | -/// - -```scala -val bt = Bit <> VAR -val bl = Boolean <> VAR -val t1 = bt && bl //result type: Bit -val t2 = bt ^ 1 //result type: Bit -val t3 = bl || false //result type: Boolean -val t4 = bt && true //result type: Bit -val t5 = bl || bt //result type: Boolean -val t6 = bl ^ 0 || !bt -//`t7` after the candidate implicit -//conversions, looks like so: -//(bl && bt.bool) ^ (!(bt || bl.bit)).bool -val t7 = (bl && bt) ^ !(bt || bl) -//error: swap argument positions to have -//the DFHDL value on the LHS. -val e1 = 0 ^ bt -//error: swap argument positions to have -//the DFHDL value on the LHS. -val e2 = false ^ bt -//not supported since both arguments -//are just candidates -val e3 = 0 ^ true -//This just yields a Scala Boolean, -//as a basic operation between Scala -//Boolean values. -val sc: Boolean = true && true -``` - -/// details | Transitioning from Verilog - type: verilog -Under the ED domain, the following operations are equivalent: - -| DFHDL Operation | Verilog Operation | -|-----------------|-------------------| -| `lhs && rhs` | `lhs & rhs` | -| `lhs || rhs` | `lhs | rhs` | -| `lhs ^ rhs` | `lhs ^ rhs` | -| `!lhs` | `!lhs` | -/// - -/// details | Transitioning from VHDL - type: vhdl -Under the ED domain, the following operations are equivalent: - -| DFHDL Operation | VHDL Operation | -|-----------------|-------------------| -| `lhs && rhs` | `lhs and rhs` | -| `lhs || rhs` | `lhs or rhs` | -| `lhs ^ rhs` | `lhs xor rhs` | -| `!lhs` | `not lhs` | -/// - -#### Constant Meta Operations - -These operations are activated during the [elaboration stage][elaboration] of the DFHDL compilation, and are only available for constant `Bit`/`Boolean` DFHDL values. -Their use case is for meta-programming purposes, to control the generated code without the knowledge of the DFHDL compiler (could be considered as pre-processing steps). - -/// html | div.operations -| Operation | Description | LHS Constraints | Returns | -| ------------ | ----------- | ------------------- | ------- | -| `lhs.toScalaBitNum` | Extracts the known elaboration Scala `BitNum`(`1 | 0`) value from a constant DFHDL `Bit`/`Boolean` value | Constant `Bit`/`Boolean` DFHDL value | Scala `BitNum` value | -| `lhs.toScalaBoolean` | Extracts the known elaboration Scala `Boolean` value from a constant DFHDL `Bit`/`Boolean` value | Constant `Bit`/`Boolean` DFHDL value | Scala `Boolean` value | -/// - -The following runnable example demonstrates how such meta operation affect the elaborated design. -The `Boolean` argument `arg` of a design `Foo` is used twice within the design: -first, in an `if` condition directly; and second, in an `if` condition after a Scala value extraction. -When referenced directly, the `if` is elaborated as-is, but when the `if` is applied on the extracted Scala value, -the `if` is completely removed and either the block inside the `if` is elaborated when the argument is true or completely removed if false. - -/// tab | `Foo` -```scala -class Foo( - val arg: Boolean <> CONST -) extends DFDesign: - val o = Bit <> OUT - if (!arg) o := 1 - if (arg.toScalaBoolean) o := 0 -``` -/// - - -/// tab | `Foo(true)` +/// admonition | `Bit` variables accept `Boolean` comparison values as condidates + type: note +All comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`) return `Boolean`, and can be directly assigned to `Bit` variables: ```scala -class Foo( - val arg: Boolean <> CONST -) extends DFDesign: - val o = Bit <> OUT - if (!arg) o := 1 - o := 0 +class Foo extends RTDesign: + val limit = UInt(8) <> IN + val counter = UInt(8) <> VAR.REG init 0 + val tick = Bit <> OUT + tick := counter == limit // Implicit Boolean -> Bit conversion ``` /// -/// tab | `Foo(false)` +/// admonition | `if` and `while` conditionals accept both `Boolean` and `Bit` values + type: note +`if` and `while` conditional expression and statements accept both `Boolean` and `Bit` values (no conversion is taking place). In stricter backends like `vhdl.v93`, an automatic conversion is applied `Boolean` where needed. ```scala -class Foo( - val arg: Boolean <> CONST -) extends DFDesign: - val o = Bit <> OUT - if (!arg) o := 1 -``` -/// - -/// details | Runnable example - type: dfhdl -```scastie -import dfhdl.* - -@top(false) class Foo( - val arg: Boolean <> CONST -) extends DFDesign: - val o = Bit <> OUT - if (!arg) o := 1 - if (arg.toScalaBoolean) o := 0 - -@main def main = - println("Foo(true) Elaboration:") - Foo(true).printCodeString - println("Foo(false) Elaboration:") - Foo(false).printCodeString +class Foo extends RTDesign: + val tick = Bit <> IN + if (tick) // if condition accepts both Bit and Boolean values + //do something + end if ``` /// -## `Bits` DFHDL Values {#DFBits} +### `Bits` {#DFBits} `Bits` DFHDL values represent vectors of DFHDL `Bit` values as elements. The vector bits width (length) is a positive constant number (nilable [zero-width] vectors will be supported in the future). @@ -864,7 +726,7 @@ in their implementations and externally in their API. Where applicable, both `Bi vector of `Bits` have overlapping equivalent APIs. /// -### DFType Constructors +#### DFType Constructors /// html | div.operations | Constructor | Description | Arg Constraints | Returns | @@ -899,11 +761,16 @@ val b6: Bits[6] <> CONST = all(0) * __Additional constructors:__ DFHDL provides additional constructs to simplify some common VHDL bit vector declaration. For example, instead of declaring `signal addr: std_logic_vector(clog2(DEPTH)-1 downto 0)` in VHDL, in DFHDL simply declare `val addr = Bits.until(DEPTH) <> VAR`. /// -### Literal (Constant) Value Generation +#### Type Signatures +- Bounded: `Bits[8]`, `Bits[4]` +- Parameterized bounded: `Bits[w.type]` (where `w: Int <> CONST`) +- Unbounded: `Bits[Int]` + +#### Literal (Constant) Value Generation Literal (constant) DFHDL `Bits` value generation is carried out through [binary][b-interp] and [hexadecimal][h-interp] string interpolation, a core [Scala feature](https://docs.scala-lang.org/scala3/book/string-interpolation.html){target="_blank"} that was customized for DFHDL's exact use-case. There are also bit-accurate [decimal][d-interp] and [signed decimal][sd-interp] interpolations available that produce `UInt` and `SInt` DFHDL values. If needed, those values can be cast to `Bits`. No octal interpolation is currently available or planned. -#### Binary Bits String-Interpolator {#b-interp} +##### Binary Bits String-Interpolator {#b-interp} ```scala linenums="0" title="Binary Bits string-interpolation syntax" b"width'bin" @@ -947,7 +814,7 @@ This interpolation covers the Verilog binary literal use-cases, but also adds th This interpolation covers the VHDL binary literal use-cases, but also adds the ability for parametric `width` to be set. The high impedance (high-Z) use-cases will be supported in the future, likely using a different language construct. /// -#### Hexadecimal Bits String-Interpolator {#h-interp} +##### Hexadecimal Bits String-Interpolator {#h-interp} ```scala linenums="0" title="Hexadecimal Bits string-interpolation syntax" h"width'hex" @@ -993,12 +860,12 @@ This interpolation covers the Verilog hexadecimal literal use-cases, but also ad This interpolation covers the VHDL hexadecimal literal use-cases, but also adds the ability for parametric `width` to be set. The high impedance (high-Z) use-cases will be supported in the future, likely using a different language construct. /// -### Candidates +#### Candidates * DFHDL `Bits` values * DFHDL `Bit` or `Boolean` values. This candidate produces a single bit `Bits[1]` vector. * DFHDL `UInt` values * Scala `Tuple` combination of any DFHDL values and `1`/`0` literal values. This candidate performs bit concatenation of all values, according their order in the tuple, encoded from the most-significant value position down to the least-significant value position. - * Application-only candidate - Same-Element Vector (`all(elem)`). + * Application-only candidate - Same-Element Vector (`all(elem)`). ```scala val b8 = Bits(8) <> VAR @@ -1021,7 +888,22 @@ val s4 = SInt(4) <> VAR b8 := (1, s4, b1, b"10") ``` -### Concatenated Assignment +/// admonition | `Bits` does not accept plain integer candidates + type: note +Unlike `UInt`/`SInt`, `Bits` values **cannot** be initialized or assigned with plain integers. Use `all(0)` for zero initialization, or a sized literal: +```scala +// CORRECT +val b8 = Bits(8) <> VAR init all(0) // zero via all(0) +val b4 = Bits(4) <> VAR init b"4'0" // zero via binary literal +val b6 = Bits(6) <> VAR init h"6'00" // zero via hex literal + +// error: An integer value cannot be a candidate for a Bits type. +// Try explicitly using a decimal constant via the `d"'"` string interpolation. +val b16 = Bits(16) <> VAR init 0 // compile error +``` +/// + +#### Concatenated Assignment DFHDL supports a special-case assignment of concatenated DFHDL Bits variables, using a Scala `Tuple` syntax on LHS of the assignment operator. Both LHS and RHS bits width must be the same. This assignment is just syntactic sugar for multiple separate assignments and carried out during the design [elaboration][elaboration]. The assignment ordering is from the first value at most-significant position down to the last value at least-significant position. /// tab | `Foo Declaration` @@ -1067,62 +949,43 @@ given options.AppOptions.AppMode = options.AppOptions.AppMode.elaborate ``` /// -## `UInt`/`SInt`/`Int` DFHDL Values {#DFDecimal} +### `UInt`/`SInt`/`Int` {#DFDecimal} DFHDL provides three decimal numeric types: -- `UInt` - Unsigned integer values -- `SInt` - Signed integer values -- `Int` - Constant integer values (used mainly for parameters) -### DFType Constructors +- `UInt` - Unsigned bit-accurate integer values +- `SInt` - Signed bit-accurate integer values +- `Int` - 32-bit integer values (used mainly for parameters) + +#### DFType Constructors /// html | div.operations | Constructor | Description | Arg Constraints | Returns | | ------------ | ----------- | ------------------- | ------- | | `UInt(width)`| Construct an unsigned integer DFType with the given `width` as number of bits. | `width` is a positive Scala `Int` or constant DFHDL `Int` value. | `UInt[width.type]` DFType | -| `UInt.until(sup)`| Construct an unsigned integer DFType with the given `sup` supremum number the value is expected to reach. The number of bits is set as `clog2(sup)`. | `sup` is a Scala `Int` or constant DFHDL `Int` value larger than 1. | `UInt[CLog2[width.type]]` DFType | -| `UInt.to(max)`| Construct an unsigned integer DFType with the given `max` maximum number the value is expected to reach. The number of bits is set as `clog2(max+1)`. | `max` is a positive Scala `Int` or constant DFHDL `Int` value. | `UInt[CLog2[width.type+1]]` DFType | +| `UInt.until(sup)`| Construct an unsigned integer DFType with the given `sup` supremum number the value is expected to reach. The number of bits is set as `clog2(sup)`. | `sup` is a Scala `Int` or constant DFHDL `Int` value **larger than 1**. `UInt.until(1)` is invalid (would produce 0-bit width). | `UInt[CLog2[width.type]]` DFType | +| `UInt.to(max)`| Construct an unsigned integer DFType with the given `max` maximum number the value is expected to reach. The number of bits is set as `clog2(max+1)`. | `max` is a positive Scala `Int` or constant DFHDL `Int` value. `UInt.to(1)` is valid (produces 1-bit width). | `UInt[CLog2[width.type+1]]` DFType | | `SInt(width)`| Construct a signed integer DFType with the given `width` as number of bits. | `width` is a positive Scala `Int` or constant DFHDL `Int` value. | `SInt[width.type]` DFType | | `Int`| Construct a constant integer DFType. Used mainly for parameters. | None | `Int` DFType | +/// + +#### Type Signatures +- Bounded: `UInt[8]`, `SInt[16]` +- Parameterized bounded: `UInt[w.type]`, `SInt[w.type]` (where `w: Int <> CONST`) +- Unbounded: `UInt[Int]`, `SInt[Int]` +- `Int` has no size parameter: `Int <> CONST`, `Int <> VAL` -### Candidates +#### Candidates * DFHDL decimal values of the same type * DFHDL `Bits` values (via `.uint` or `.sint` casting) * Scala numeric values (Int, Long, etc.) for constant values - * Decimal string interpolation values + * Decimal literals (string interpolation values) -### Operations +#### Constant Generation -#### Arithmetic Operations -These operations propagate constant modifiers and maintain proper bit widths: - -/// html | div.operations -| Operation | Description | LHS/RHS Constraints | Returns | -| ------------ | ----------- | ------------------- | ------- | -| `lhs + rhs` | Addition | Both decimal types | Result with appropriate width | -| `lhs - rhs` | Subtraction | Both decimal types | Result with appropriate width | -| `lhs * rhs` | Multiplication | Both decimal types | Result with width = lhs.width + rhs.width | -| `lhs / rhs` | Division | Both decimal types | Result with lhs width | -| `lhs % rhs` | Modulo | Both decimal types | Result with rhs width | +##### Decimal String-Interpolator {#d-interp} -#### Comparison Operations -Return Boolean values: - -/// html | div.operations -| Operation | Description | LHS/RHS Constraints | Returns | -| ------------ | ----------- | ------------------- | ------- | -| `lhs < rhs` | Less than | Both decimal types | Boolean | -| `lhs <= rhs` | Less than or equal | Both decimal types | Boolean | -| `lhs > rhs` | Greater than | Both decimal types | Boolean | -| `lhs >= rhs` | Greater than or equal | Both decimal types | Boolean | -| `lhs == rhs` | Equal | Both decimal types | Boolean | -| `lhs != rhs` | Not equal | Both decimal types | Boolean | - -### Constant Generation - -#### Decimal String-Interpolator {#d-interp} - -The decimal string interpolator `d` creates unsigned/signed integer constants (`UInt`) from decimal values. +The decimal string interpolator `d` creates unsigned/signed integer constants (`UInt`) from decimal values. ```scala linenums="0" title="Decimal string-interpolation syntax" d"width'dec" @@ -1146,7 +1009,7 @@ d"1,023" // UInt[10], value = 1023 d"1_000" // UInt[10], value = 1000 ``` -#### Signed Decimal String-Interpolator {#sd-interp} +##### Signed Decimal String-Interpolator {#sd-interp} The signed decimal string interpolator `sd` creates signed integer constants (`SInt`) from decimal values. @@ -1170,7 +1033,7 @@ sd"8'42" // SInt[8], value = 42 sd"8'255" // Error: width too small to represent value with sign bit ``` -### Examples +#### Examples ```scala // Basic declarations @@ -1192,18 +1055,21 @@ val u4 = UInt(4) <> VAR init d"4'10" val s4 = SInt(4) <> VAR init sd"4'-2" ``` -## Enumeration DFHDL Values {#DFEnum} +### Enumeration {#DFEnum} DFHDL supports enumerated types through Scala's enum feature with special encoding traits. Enums provide a type-safe way to represent a fixed set of values. -### Enum Type Definition +#### Enum Type Definition ```scala enum MyEnum extends Encoded: case A, B, C, D ``` -### Encoding Types +#### Type Signatures +`MyEnum <> VAL`, `MyEnum <> CONST`. The enum name itself is the type — no size parameter. + +#### Encoding Types DFHDL supports several encoding schemes for enums: @@ -1239,17 +1105,9 @@ enum MyEnum(val value: UInt[8] <> CONST) extends Encoded.Manual(8): case C extends MyEnum(50) ``` -### Operations + Note: the Manual encoding enum class **must** declare a constructor parameter `(val value: UInt[N] <> CONST)` and the bit width `N` must match the argument to `Encoded.Manual(N)`. Each `case` must explicitly extend the enum class and pass a constant value. Omitting the constructor parameter will cause a compile error. -/// html | div.operations -| Operation | Description | LHS/RHS Constraints | Returns | -| ------------ | ----------- | ------------------- | ------- | -| `lhs == rhs` | Equality comparison | Same enum type | Boolean | -| `lhs != rhs` | Inequality comparison | Same enum type | Boolean | -| `lhs.bits` | Get raw bits representation | Enum value | Bits | -| `lhs.uint` | Get unsigned int representation | Enum value | UInt | - -### Pattern Matching +#### Pattern Matching Enums can be used in pattern matching expressions: @@ -1262,7 +1120,7 @@ state match case MyEnum.C => // handle C ``` -### Examples +#### Examples ```scala // State machine enum @@ -1283,11 +1141,11 @@ class CPU extends RTDesign: // Store state logic ``` -## Vector DFHDL Values {#DFVector} +### Vector {#DFVector} DFHDL vectors allow creating arrays of any DFHDL type. Unlike `Bits` which is specialized for bit vectors, generic vectors can hold any DFHDL type and support multi-dimensional arrays. -### Vector Type Construction +#### Vector Type Construction The vector type is constructed using the `X` operator between a base type and dimension: @@ -1302,7 +1160,13 @@ val vec2 = Bit X 8 X 8 <> VAR // 2D 8x8 vector of bits val vec3 = MyEnum X 16 <> VAR // Vector of 16 enum values ``` -### Initialization +#### Type Signatures +- Bounded: `UInt[8] X 4`, `Bits[8] X 4 X 4` +- Parameterized bounded: `UInt[4] X len.type` (where `len: Int <> CONST`) +- Unbounded: `Bits[8] X Int` +- Both element type and dimensions can be parameterized independently + +#### Initialization Vectors can be initialized in several ways: @@ -1317,26 +1181,7 @@ val vec2 = UInt(8) X 4 <> VAR init Vector(1, 2, 3, 4) val mem = UInt(32) X 1024 <> VAR initFile "mem.hex" ``` -### Operations - -#### Element Access -Access individual elements using array indexing: - -```scala -val elem = vec(idx) // Read element at index -vec(idx) := newValue // Write element at index -``` - -#### Vector-wide Operations - -/// html | div.operations -| Operation | Description | Returns | -| ------------ | ----------- | ------- | -| `vec.elements` | Get all elements as Scala sequence | Seq[BaseType] | -| `vec.size` | Get vector dimension | Int | -| `vec.bits` | Get bits representation | Bits | - -### Multi-dimensional Vectors +#### Multi-dimensional Vectors Multi-dimensional vectors are created by chaining `X` operators: @@ -1352,7 +1197,7 @@ matrix(1)(2) := 42 matrix := all(all(0)) // All elements to 0 ``` -### Memory/RAM Implementation +#### Memory/RAM Implementation Vectors are commonly used to implement memories and RAMs: @@ -1368,7 +1213,7 @@ class RAM extends RTDesign: data := mem(addr) // Read ``` -### File Initialization +#### File Initialization Vectors support initialization from files in various formats: @@ -1380,11 +1225,11 @@ val rom = UInt(8) X 256 <> VAR initFile("rom.hex", InitFileFormat.VerilogHex) val ram = UInt(32) X 1024 <> VAR initFile "ram.bin" ``` -## Struct DFHDL Values {#DFStruct} +### Struct {#DFStruct} DFHDL structures allow creating composite types by combining multiple DFHDL values into a single type. Structs are defined using Scala case classes that extend the `Struct` trait. -### Struct Type Definition +#### Struct Type Definition ```scala case class MyStruct( @@ -1394,7 +1239,10 @@ case class MyStruct( ) extends Struct ``` -### Field Access and Assignment +#### Type Signatures +`MyStruct <> VAL`, `MyStruct <> CONST`. The struct name is the type. Struct fields must use bounded types (no `UInt[Int]` in fields). + +#### Field Access and Assignment Fields are accessed using dot notation and can be assigned individually: @@ -1405,7 +1253,7 @@ s.field2 := b"1010" // Assign bits s := MyStruct(1, b"0101", true) // Assign whole struct ``` -### Nested Structs +#### Nested Structs Structs can be nested to create more complex data structures: @@ -1418,16 +1266,7 @@ rect.topLeft.x := 0 rect.bottomRight.y := 100 ``` -### Operations - -/// html | div.operations -| Operation | Description | LHS/RHS Constraints | Returns | -| ------------ | ----------- | ------------------- | ------- | -| `lhs == rhs` | Equality comparison | Same struct type | Boolean | -| `lhs != rhs` | Inequality comparison | Same struct type | Boolean | -| `lhs.bits` | Get raw bits representation | Struct value | Bits | - -### Pattern Matching +#### Pattern Matching Structs support pattern matching for field extraction: @@ -1438,7 +1277,7 @@ point match case Point(0, _) => // Match x=0, any y ``` -### Examples +#### Examples ```scala // AXI-like interface struct @@ -1460,17 +1299,20 @@ class MyDesign extends RTDesign: axi.data := h"DEADBEEF" ``` -## Tuple DFHDL Values {#DFTuple} +### Tuple {#DFTuple} DFHDL tuples provide a way to group multiple DFHDL values together without defining a named structure. They are similar to Scala tuples but operate on DFHDL values. -### Tuple Type Construction +#### Tuple Type Construction ```scala val tuple = (Type1, Type2, ..., TypeN) <> Modifier ``` -### Examples +#### Type Signatures +`(UInt[8], Bit) <> VAL`, `(Bits[Int], Bit) <> CONST`. Elements can be individually bounded or unbounded. + +#### Examples ```scala // Basic tuple declaration @@ -1484,7 +1326,7 @@ pair := (42, 1) complex := ((100, 0), b"1010") ``` -### Element Access +#### Element Access Tuple elements can be accessed using ._N notation or pattern matching: @@ -1496,20 +1338,11 @@ val second = pair._2 // Access second element val (x, y) = pair ``` -### Operations - -/// html | div.operations -| Operation | Description | Returns | -| ------------ | ----------- | ------- | -| `tuple.bits` | Get bits representation | Bits | -| `tuple == other` | Equality comparison | Boolean | -| `tuple != other` | Inequality comparison | Boolean | - -## Opaque DFHDL Values {#DFOpaque} +### Opaque {#DFOpaque} Opaque types allow creating new DFHDL types that wrap existing types while hiding their internal representation. This is useful for creating abstraction layers and type-safe interfaces. -### Opaque Type Definition +#### Opaque Type Definition ```scala // Define opaque type wrapping UInt(8) @@ -1522,7 +1355,10 @@ case class Counter() extends Opaque(UInt(32)): (c.actual + 1).as(Counter) ``` -### Usage +#### Type Signatures +`MyOpaque <> VAL`, `MyOpaque <> CONST`. The opaque name is the type. + +#### Usage ```scala val op = MyOpaque <> VAR @@ -1530,7 +1366,7 @@ val wrapped: UInt[8] <> VAL = op.actual // Access wrapped value op := 42.as(MyOpaque) // Assign using .as conversion ``` -### Examples +#### Examples ```scala // AES byte type with custom operations @@ -1547,42 +1383,24 @@ class AESCircuit extends DFDesign: out := in1 + in2 // Uses custom + operation ``` -## Double DFHDL Values {#DFDouble} +### Double {#DFDouble} DFHDL Double values represent IEEE-754 double-precision floating-point numbers. -### Type Construction +#### Type Construction ```scala val d = Double <> Modifier ``` -### Operations - -Supports standard arithmetic operations: - -```scala -val d1 = Double <> VAR -val d2 = Double <> VAR - -val sum = d1 + d2 -val prod = d1 * d2 -val quot = d1 / d2 -val comp = d1 < d2 -``` - -### Conversion - -```scala -val bits = d1.bits // Get bits representation -val d3 = bits.as(Double) // Convert back to Double -``` +#### Type Signatures +`Double <> VAL`, `Double <> CONST`. No size parameter (always 64 bits). -## Time/Freq DFHDL Values {#DFPhysical} +### Time/Freq {#DFPhysical} DFHDL provides special types for representing time and frequency values in hardware designs through physical units. These types help ensure correct timing specifications and frequency calculations. -### Time Values +#### Time Values Time values can be created using various unit suffixes: @@ -1604,7 +1422,7 @@ val t9 = 1.5.ns // 1.5 nanoseconds val t10 = 10.ms // 10 milliseconds ``` -### Frequency Values +#### Frequency Values Frequency values can be specified using standard frequency units: @@ -1622,7 +1440,7 @@ val f5 = 100.MHz // 100 megahertz val f6 = 2.5.GHz // 2.5 gigahertz ``` -### Usage in RT Domains +#### Usage in RT Domains Physical values are particularly useful when configuring RT domains and specifying clock frequencies: @@ -1641,7 +1459,7 @@ class TimingExample extends RTDesign: val clockFreq = 1.GHz // Clock frequency ``` -### Cycles in RT Domain +#### Cycles in RT Domain In RT domains, you can also specify cycle counts using the `.cy` unit: @@ -1652,11 +1470,11 @@ class RTExample extends RTDesign: Note: The `.cy` unit is only available within register-transfer (RT) domains. -## Unit (Void) DFHDL Values {#DFUnit} +### Unit (Void) {#DFUnit} The Unit type in DFHDL represents a void or no-value type, similar to Scala's Unit type. It's typically used when an operation doesn't need to return a meaningful value. -### Usage +#### Usage ```scala // Method returning Unit @@ -1669,7 +1487,7 @@ val x = Bit <> VAR val y: Unit <> VAL = x := 1 ``` -### Common Use Cases +#### Common Use Cases 1. Side-effect operations 2. Void method returns @@ -1684,3 +1502,905 @@ class Example extends EDDesign: // Process body returns Unit doSomething ``` +## Operations + +### Conversions and Casts {#type-conversion} +The diagram below shows the conversion/cast paths between DFHDL types. Solid arrows are simple casts that preserve width; dashed arrows involve width changes. + +![type-conversion](type-conversion-light.svg#only-light){ width="70%" } +![type-conversion](type-conversion-dark.svg#only-dark){ width="70%" } + +/// html | div.conversion +| From | To | Method | From | To | Method | +|------|-----|--------|------|-----|--------| +| `T` | `Bits` | `.bits` | `Bit` | `Boolean` | `.bool` | +| `Bits` | `T` | `.as(T)` | `Boolean` | `Bit` | `.bit` | +| `Bits(w)` | `UInt(w)` | `.uint` | `Bit`/`Boolean` | `Bits(1)` | `.bits` | +| `Bits(w)` | `SInt(w)` | `.sint` | `Bit`/`Boolean` | `Bits(w)` | `.toBits(w)` | +| `UInt(w)` | `SInt(w+1)` | `.signed` | `Bit`/`Boolean` | `UInt(w)` | `.toUInt(w)` | +| `UInt`/`SInt` | `Int` | `.ToInt` | `Bit`/`Boolean` | `SInt(w)` | `.toSInt(w)` | +/// + +#### Any Type to/from `Bits`: `.bits` and `.as(T)` {#bits-cast} + +Every DFHDL type can be converted to its raw bit representation with `.bits`. The inverse operation, `.as(T)`, reinterprets a `Bits` value as a target type `T`, provided the bit widths match exactly: + +```scala +val u8 = UInt(8) <> VAR +val b8 = u8.bits // UInt(8) -> Bits(8) +val back = b8.as(UInt(8)) // Bits(8) -> UInt(8) +``` + +This also works with composite types such as enums, structs, and opaques: + +```scala +val e = MyEnum <> VAR +val eBits = e.bits // Enum -> Bits +val eBack = eBits.as(MyEnum) // Bits -> Enum +``` + +#### `Bits` to `UInt`/`SInt`: `.uint` and `.sint` {#uint-sint-cast} + +These are shorthand conversions from `Bits` that preserve width. The same bits are simply reinterpreted as unsigned or signed: + +```scala +val b8 = Bits(8) <> VAR +val u8 = b8.uint // Bits(8) -> UInt(8), same bit pattern +val s8 = b8.sint // Bits(8) -> SInt(8), same bit pattern +``` + +#### `UInt` to `SInt`: `.signed` {#signed-cast} + +Converting an unsigned value to signed requires an extra bit for the sign, so `.signed` widens the result by one bit: + +```scala +val u8 = UInt(8) <> VAR +val s9 = u8.signed // UInt(8) -> SInt(9) +``` + +To get an `SInt` with the **same** width (reinterpreting the bit pattern without expanding), go through `Bits`: + +```scala +val s8 = u8.bits.sint // UInt(8) -> Bits(8) -> SInt(8) +``` + +#### `Bit` and `Boolean` Conversions {#bit-bool-cast} + +`Bit` is the hardware single-bit type and `Boolean` is the logical type. They are convertible to each other with `.bit` and `.bool`: + +```scala +val myBit = Bit <> VAR +val myBool = myBit.bool // Bit -> Boolean +val back = myBool.bit // Boolean -> Bit +``` + +Both `Bit` and `Boolean` can be widened (zero-extended) into `Bits`, `UInt`, or `SInt` with an explicit target width: + +```scala +val flag = Bit <> VAR +val b4 = flag.toBits(4) // Bit -> Bits(4) +val u4 = flag.toUInt(4) // Bit -> UInt(4) +val s4 = flag.toSInt(4) // Bit -> SInt(4) +``` + +When the value is `1`, these produce the value `1` at the given width (not sign-extended). The single-bit `.bits` conversion is also available, returning `Bits(1)`. + +#### Enum to `UInt`: `.uint` {#enum-uint-cast} + +Enum values can be converted to their underlying unsigned integer representation: + +```scala +val e = MyEnum <> VAR +val u = e.uint // Enum -> UInt (encoding-dependent width) +``` + +### Bit Selection and Slicing {#common-bit-vector-ops} + +Applies to: `Bits`, `UInt`, `SInt` + +- **Range slice**: `value(hi, lo)` extracts bits `hi` down to `lo`, returning a narrower value of the **same type** (`Bits` → `Bits`, `UInt` → `UInt`, `SInt` → `SInt`). +- **Single-bit access**: `value(idx)` returns the bit at position `idx` (as `Bit`). The index can be a static integer or a dynamic `UInt` variable. + +```scala +val b8 = Bits(8) <> VAR +val u8 = UInt(8) <> VAR +val s8 = SInt(8) <> VAR + +// Range slicing — preserves the original type +val b4 = b8(7, 4) // Bits[4]: upper nibble +val u4 = u8(3, 0) // UInt[4]: lower nibble +val s4 = s8(3, 0) // SInt[4]: lower nibble + +// Single-bit access +val msb = b8(7) // Bit +val lsb = u8(0) // Bit + +// Dynamic bit access (index is a UInt variable) +val idx = UInt(3) <> VAR +val dynbit = b8(idx) // Bit at position idx +``` + +/// admonition | Dynamic bit indexing + type: tip +You can index into a bit-vector value using a `UInt` variable, not just integer literals. The index must be a `UInt` whose width equals `clog2(bits_width)`. For example, indexing into `Bits(8)` requires a `UInt(3)` index. If the width does not match, the compiler will report an error and suggest using `.truncate` (to narrow) or `.extend` (to widen). + +Dynamic indexing works for both reads and writes: +```scala +val data = Bits(8) <> VAR init all(0) +val pos = UInt(3) <> VAR init 0 +val din = Bit <> IN + +val bit_out = data(pos) // dynamic read +process(clk): + if (clk.rising) + data(pos) :== din // dynamic write +``` + +When the index variable is wider than needed, use `.truncate` to automatically narrow it to the required width: +```scala +val data = Bits(8) <> VAR init all(0) +val pos = UInt(4) <> VAR init 0 // 4-bit, but Bits(8) needs UInt(3) +val bit_out = data(pos.truncate) // .truncate narrows to UInt(3) automatically +``` +/// + +### Width Adjustment {#width-adjustment} + +Applies to: `Bits`, `UInt`, `SInt` + +- `.resize(N)` sets the width to exactly `N` bits. For `UInt` and `Bits`, widening zero-extends; for `SInt`, widening sign-extends. Narrowing truncates the most-significant bits. +- `.truncate` automatically narrows to the width expected by the assignment or operation context. +- `.extend` automatically widens to the width expected by the context. + +```scala +val b8 = Bits(8) <> VAR +val b4 = Bits(4) <> VAR +b4 := b8.resize(4) // truncate to 4 bits +b8 := b4.extend // auto-widen to match b8's width + +val u8 = UInt(8) <> VAR +val u6 = UInt(6) <> VAR +u6 := u8.truncate // auto-narrow to match u6's width +u8 := u6.resize(8) // explicit zero-extend to 8 bits + +val s8 = SInt(8) <> VAR +val s4 = SInt(4) <> VAR +s8 := s4.extend // sign-extend to match s8's width +s4 := s8.resize(4) // truncate to 4 bits +``` + +/// admonition | `.truncate` is not `.resize(N)` -- do not pass an argument to `.truncate` + type: warning +`.truncate` (no argument) narrows the width to match the assignment context. There is no `.truncate(N)` method. Writing `expr.truncate(N)` is parsed by Scala as `expr.truncate` followed by `(N)`, which applies bit selection on the truncated result -- selecting a single bit at index `N`. This causes confusing errors like "argument must be smaller than the upper-bound". To keep the lowest `N` bits, use `.resize(N)`: + +```scala +val b8 = Bits(8) <> VAR +val b4 = Bits(4) <> VAR +b4 := b8.truncate // OK: auto-narrow from 8 to 4 bits +b4 := b8.resize(4) // OK: explicit narrow to 4 bits +// b8.truncate(4) // MISLEADING: this is b8.truncate followed by bit-select (4) +``` +/// + +### Bit Concatenation {#bit-concat} + +Applies to: `Bits`, `UInt`, `SInt` + +Multiple bit-vector values can be concatenated using Scala tuple syntax with `.toBits`: + +```scala +val concat = (b"100", b"1", b"0", b"11").toBits // Bits[8] +val u8 = UInt(8) <> VAR +val u4 = UInt(4) <> VAR +val wide = (u8, u4).toBits // Bits[12] +``` + +Values are concatenated from the first (most-significant) to the last (least-significant) position. + +### Logical Operations {#logical-ops} + +Applies to: `Bit`, `Boolean` + +Logical operations' return type always matches the LHS argument's type. +These operations propagate constant modifiers, meaning that if all arguments are constant, the returned value is also a constant. + +/// html | div.operations +| Operation | Description | LHS/RHS Constraints | Returns | +| ------------ | ----------- | ------------------- | ------- | +| `lhs && rhs` | Logical AND | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `lhs || rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `lhs & rhs` | Logical AND | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `lhs | rhs` | Logical OR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `lhs ^ rhs` | Logical XOR | The LHS argument must be a `Bit`/`Boolean` DFHDL value. The RHS must be a `Bit`/`Boolean` candidate. | LHS-Type DFHDL value | +| `!lhs` | Logical NOT | The argument must be a `Bit`/`Boolean` DFHDL value. | LHS-Type DFHDL value | +/// + +```scala +val bt = Bit <> VAR +val bl = Boolean <> VAR +val t1 = bt && bl //result type: Bit +val t2 = bt ^ 1 //result type: Bit +val t3 = bl || false //result type: Boolean +val t4 = bt && true //result type: Bit +val t5 = bl || bt //result type: Boolean +val t6 = bl ^ 0 || !bt +//`t7` after the candidate implicit +//conversions, looks like so: +//(bl && bt.bool) ^ (!(bt || bl.bit)).bool +val t7 = (bl && bt) ^ !(bt || bl) +//error: swap argument positions to have +//the DFHDL value on the LHS. +val e1 = 0 ^ bt +//error: swap argument positions to have +//the DFHDL value on the LHS. +val e2 = false ^ bt +//not supported since both arguments +//are just candidates +val e3 = 0 ^ true +//This just yields a Scala Boolean, +//as a basic operation between Scala +//Boolean values. +val sc: Boolean = true && true +``` + +/// admonition | Logical `||`/`&&` and bitwise `|`/`&` on Bit and Boolean values + type: tip +In DFHDL, the operators `||` and `&&` are equivalent to `|` and `&`, respectively, when applied on either DFHDL `Bit` or `Boolean` types. In Verilog, the actual operator printed depends on the LHS argument of the operation: if it's `Bit`, the operator will be `|`/`&`; if it's `Boolean`, the operator will be `||`/`&&`. +/// + +/// details | Transitioning from Verilog + type: verilog +Under the ED domain, the following operations are equivalent: + +| DFHDL Operation | Verilog Operation (Bit LHS) | Verilog Operation (Boolean LHS) | +|-----------------|-----------------------------|---------------------------------| +| `lhs && rhs` | `lhs & rhs` | `lhs && rhs` | +| `lhs || rhs` | `lhs | rhs` | `lhs || rhs` | +| `lhs & rhs` | `lhs & rhs` | `lhs && rhs` | +| `lhs | rhs` | `lhs | rhs` | `lhs || rhs` | +| `lhs ^ rhs` | `lhs ^ rhs` | `lhs ^ rhs` | +| `!lhs` | `!lhs` | `!lhs` | +/// + +/// details | Transitioning from VHDL + type: vhdl +Under the ED domain, the following operations are equivalent: + +| DFHDL Operation | VHDL Operation | +|-----------------|-------------------| +| `lhs && rhs` | `lhs and rhs` | +| `lhs || rhs` | `lhs or rhs` | +| `lhs ^ rhs` | `lhs xor rhs` | +| `!lhs` | `not lhs` | +/// + +### Bit Reduction Operations (`.&`, `.|`, `.^`) {#reduction-ops} + +Applies to: `Bits`, `UInt` (via implicit conversion to `Bits`) + +Reduction operators fold all bits of a `Bits` vector into a single `Bit` value. They are the DFHDL equivalents of Verilog's unary reduction operators (`&v`, `|v`, `^v`): + +/// html | div.operations +| Operation | Description | Returns | +| --------- | ----------- | ------- | +| `bits.&` | AND reduction -- `1` if all bits are `1` | `Bit` | +| `bits.|` | OR reduction -- `1` if any bit is `1` | `Bit` | +| `bits.^` | XOR reduction -- `1` if an odd number of bits are `1` (parity) | `Bit` | +/// + +```scala +val b8 = Bits(8) <> VAR +val allSet = b8.& // Bit: 1 when all bits are 1 +val anySet = b8.| // Bit: 1 when at least one bit is 1 +val parity = b8.^ // Bit: 1 when odd number of bits are 1 +``` + +/// details | Transitioning from Verilog + type: verilog + +| Verilog | DFHDL | Notes | +|---------|-------|-------| +| `&v` (AND reduce) | `v.&` | All bits must be `1` | +| `|v` (OR reduce) | `v.|` | At least one bit is `1` | +| `^v` (XOR reduce) | `v.^` | Parity (odd number of `1`s) | +| `~&v` (NAND reduce) | `!v.&` | Not all bits are `1` | +| `~|v` (NOR reduce) | `!v.|` | No bits are `1` | +| `~^v` (XNOR reduce) | `!v.^` | Even parity | + +/// + +### Selection (`.sel`) {#sel-ops} + +Condition: `Bit`, `Boolean`. Arguments: any DFHDL type. + +The `.sel` operation is a conditional selection — equivalent to Verilog's ternary operator `cond ? onTrue : onFalse`. It selects between two values based on a `Bit` or `Boolean` condition: + +/// html | div.operations +| Operation | Description | Returns | +| --------- | ----------- | ------- | +| `cond.sel(onTrue, onFalse)` | Select `onTrue` when `cond` is true/1, `onFalse` otherwise | Same type as the arguments | +/// + +The `onTrue` and `onFalse` arguments can be any DFHDL type — `UInt`, `SInt`, `Bits`, `Enum`, `Struct`, etc. They can also be Scala literals constant parameters. The result type is determined by whichever argument is a DFHDL value (the other is auto-converted via type conversion): + +```scala +val flag = Boolean <> VAR +val u8 = UInt(8) <> VAR + +// Select between two literals +val r1 = flag.sel(11, d"4'12") // UInt[4]: 11 if true, 12 if false + +// Select between DFHDL values +val r2 = flag.sel(u8, d"8'0") // UInt[8]: u8 if true, 0 if false + +// Select with Int parameters +val c1: Int <> CONST = 1 +val c2: Int <> CONST = 2 +val r3 = flag.sel(c1, c2) // Int: c1 if true, c2 if false + +// Select with other types +val e = flag.sel(MyEnum.A, MyEnum.B) // MyEnum +``` + +/// admonition | Prefer `if`/`match` for complex conditions + type: tip +For simple one-level selections, `.sel` is concise and maps directly to Verilog's ternary. However, nesting or chaining `.sel` operations (e.g., `a.sel(b.sel(x, y), z)`) quickly becomes unreadable. For complex conditional logic, use `if`/`else` or `match` expressions instead — they are clearer and produce equivalent hardware. +/// + +/// details | Transitioning from Verilog + type: verilog +The `.sel` operation compiles to Verilog's ternary operator: + +| DFHDL | Verilog | +|-------|---------| +| `cond.sel(a, b)` | `cond ? a : b` | +/// + +/// details | Transitioning from VHDL + type: vhdl +VHDL has no equivalent to Verilog's ternary expression. The DFHDL-generated VHDL package includes `bool_sel` functions that implement this behavior, with dedicated overloads generated for each type as required. + +| DFHDL | Generated VHDL | +|-------|----------------| +| `cond.sel(a, b)` | `bool_sel(cond, a, b)` | +/// + +### Arithmetic Operations (`+`, `-`, `*`, `/`, `%`) {#arithmetic-ops} + +Applies to: `UInt`, `SInt`, `Bits` (via implicit conversion to `UInt`), `Int`, `Double` (`%` not available for `Double`) + +/// html | div.decimal_arithmetic +| Operation | Description | Returns | +| ------------ | -------------- | ---------------- | +| `lhs + rhs` | Addition | Same type as LHS | +| `lhs - rhs` | Subtraction | Same type as LHS | +| `lhs * rhs` | Multiplication | Same type as LHS | +| `lhs / rhs` | Division | Same type as LHS | +| `lhs % rhs` | Modulo | Same type as LHS | +/// + +#### Bit-Accurate Type Constraints (`UInt`, `SInt`) + +The result of a standard arithmetic operation on bit-accurate types always has the **same type as the LHS** operand. The RHS is resized to match the LHS before the operation is applied. + +**Sign rule** -- The LHS sign must be greater than or equal to the RHS sign (`signed >= unsigned`): + +/// html | div.operations +| LHS | RHS | Allowed | Note | +| --- | --- | ------- | ---- | +| `UInt[W1]` | `UInt[W2]` | Yes | W1 >= W2 | +| `SInt[W1]` | `SInt[W2]` | Yes | W1 >= W2 | +| `SInt[W1]` | `UInt[W2]` | Yes | W1 >= W2 + 1 (RHS is implicitly widened by 1 bit for the sign bit) | +| `UInt[W1]` | `SInt[W2]` | **No** | Compile error: an explicit conversion is required | +/// + +**Width rule** -- The LHS width must be greater than or equal to the (effective) RHS width. When applying `SInt op UInt`, the effective RHS width is `RHS width + 1` because the unsigned value gains an implicit sign bit. + +**Scala `Int` literals** are auto-promoted to a matching DFHDL type with the minimum required bit width. A negative `Int` literal on the RHS of a `UInt` operation is a compile error (unsigned LHS cannot accept a signed RHS). + +/// admonition | `Bits` values in arithmetic + type: tip +`Bits` values are implicit `UInt` candidates, so they can participate in arithmetic directly. The compiler automatically inserts `.uint` conversions on the operands and `.bits` on the result: +```scala +val i = Bits(8) <> IN +val o = Bits(8) <> OUT +o := i + i +``` +Elaborates to: +```scala +o := (i.uint + i.uint).bits +``` +/// + +```scala +val u8 = UInt(8) <> VAR +val u4 = UInt(4) <> VAR +val s8 = SInt(8) <> VAR + +// UInt + UInt (same width) +val r1 = u8 + u8 // UInt[8] +// SInt + SInt +val r2 = s8 + s8 // SInt[8] +// SInt + UInt: RHS widened to 5 bits (4+1); 8 >= 5, OK +val r3 = s8 + u4 // SInt[8] +// UInt + Scala Int literal (200 fits in 8 bits) +val r4 = u8 + 200 // UInt[8] +// Scala Int literal on LHS (200 is promoted to UInt[8]) +val r5 = 200 - u8 // UInt[8] + +// error: Cannot apply this operation between an unsigned +// value (LHS) and a signed value (RHS). +// An explicit conversion must be applied. +val e1 = u8 + s8 +// error: The applied RHS value width (8) is larger than +// the LHS variable width (4). +val e2 = u4 + u8 +// error: Cannot apply this operation between an unsigned +// value (LHS) and a signed value (RHS). +// An explicit conversion must be applied. +val e3 = u8 + (-22) + +// Int arithmetic +val param: Int <> CONST = 10 +val r6 = param * 2 // Int <> CONST = 20 +val r7 = param % 3 // Int <> CONST = 1 + +// Double arithmetic +val d1 = Double <> VAR +val d2 = Double <> VAR +val r8 = d1 + d2 // Double +val r9 = d1 / d2 // Double +``` + +/// admonition | Overflow and automatic carry promotion + type: warning +Standard arithmetic operations wrap on overflow. For example, `d"8'255" + d"8'1"` produces `d"8'0"`. Use the carry variants (`+^`, `-^`, `*^`) described below to get a wider result that preserves the full value. + +However, when an **anonymous** arithmetic expression (`+`, `-`, `*`) is assigned or connected to a variable that is **wider** than the operation's result, the operation is **automatically promoted** to a carry operation. This matches Verilog's behavior where the assignment target width determines the operation width. The carry result is then resized to fit the target if needed. + +```scala +val u8 = UInt(8) <> VAR +val u9 = UInt(9) <> VAR +val u12 = UInt(12) <> VAR +val u16 = UInt(16) <> VAR +u9 := u8 + u8 // promoted to carry addition (width 9), exact fit +u16 := u8 * u8 // promoted to carry multiplication (width 16), exact fit +u12 := u8 * u8 // promoted to carry multiplication (width 16), resized to 12 + +// Named expressions are NOT promoted: +val sum = u8 + u8 // UInt[8], named value +u9 := sum // resized from 8 to 9, no carry promotion +``` +/// + +### Carry Arithmetic (`+^`, `-^`, `*^`) {#carry-ops} + +Applies to: `UInt`, `SInt` + +Carry operations widen the result to prevent overflow. Unlike standard arithmetic, carry operations require both operands to have the **same sign**. + +/// html | div.operations +| Operation | Description | Result Width | Result Sign | +| ------------- | -------------------- | ------------------ | -------------------- | +| `lhs +^ rhs` | Carry Addition | max(LW, RW) + 1 | Same sign as operands | +| `lhs -^ rhs` | Carry Subtraction | max(LW, RW) + 1 | Same sign as operands | +| `lhs *^ rhs` | Carry Multiplication | LW + RW | Same sign as operands | +/// + +```scala +val u8 = UInt(8) <> VAR + +// Carry addition: width = max(8, 8) + 1 = 9 +val r1 = u8 +^ u8 // UInt[9] +// d"8'255" +^ d"8'1" == d"9'256" (no overflow) + +// Carry subtraction: width = max(8, 8) + 1 = 9 +val r2 = u8 -^ u8 // UInt[9] + +// Carry multiplication: width = 8 + 8 = 16 +val r3 = u8 *^ u8 // UInt[16] + +// Scala Int literal: 100 needs 7 bits +// width = 7 + 8 = 15 +val r4 = 100 *^ u8 // UInt[15] + +val s8 = SInt(8) <> VAR +val r5 = s8 +^ s8 // SInt[9] +val r6 = s8 *^ s8 // SInt[16] +``` + +/// admonition | Carry vs standard sign rules + type: tip +Unlike standard arithmetic where `SInt op UInt` is allowed (the unsigned RHS is implicitly widened), carry operations require both operands to have the **same sign**. This is because carry operations produce a wider result, and mixed-sign widening semantics would be ambiguous. Scala `Int` literals are still accepted when the literal's sign matches the DFHDL value's sign. +/// + +### Comparison Operations (`==`, `!=`, `<`, `>`, `<=`, `>=`) {#comparison-ops} + +Applies to: `UInt`, `SInt`, `Int`, `Double` (all comparisons); `Bits`, `Enum`, `Struct`, `Tuple` (`==`/`!=` only) + +#### Decimal Comparisons + +Comparison operations on `UInt`/`SInt` return a `Boolean` DFHDL value and have **stricter constraints** than arithmetic: + +/// html | div.operations +| Operation | Description | Returns | +| ------------- | ---------------------- | -------- | +| `lhs == rhs` | Equal | Boolean | +| `lhs != rhs` | Not equal | Boolean | +| `lhs < rhs` | Less than | Boolean | +| `lhs > rhs` | Greater than | Boolean | +| `lhs <= rhs` | Less than or equal | Boolean | +| `lhs >= rhs` | Greater than or equal | Boolean | +/// + +Unlike arithmetic operations which use relaxed rules (LHS sign >= RHS sign, LHS width >= RHS width), comparisons require **exact matching**: + +- **Sign:** Must match exactly (`UInt` with `UInt`, `SInt` with `SInt`). +- **Width:** Must match exactly (both operands must have the same bit width). +- **Scala `Int` literals:** The literal's bit width (adjusted +1 if the DFHDL value is signed and the literal is positive) must fit within the DFHDL value's width. + +```scala +val u8 = UInt(8) <> VAR +val u4 = UInt(4) <> VAR +val s8 = SInt(8) <> VAR + +val c1 = u8 == u8 // Boolean: same sign, same width +val c2 = u8 < 200 // Boolean: 200 fits in UInt[8] +val c3 = 0 < u8 // Boolean: Scala Int on LHS +val c4 = s8 >= 1 // Boolean: 1 is promoted to SInt (width 2 fits in 8) + +// error: Cannot apply this operation between an unsigned +// value (LHS) and a signed value (RHS). +// An explicit conversion must be applied. +val e1 = u8 == s8 +// error: Cannot apply this operation between a value of +// 8 bits width (LHS) to a value of 4 bits width (RHS). +// An explicit conversion must be applied. +val e2 = u8 == u4 +// error: Cannot compare a DFHDL value (width = 8) with a +// Scala `Int` argument that is wider (width = 10). +// An explicit conversion must be applied. +val e3 = u8 > 1000 +``` + +/// details | Scala `Int` constants auto-lift in comparisons + type: note +Plain Scala `Int` values can be used directly in comparisons and arithmetic with DFHDL typed variables. No explicit coercion is needed: +```scala +val LIMIT: Int <> CONST = 5208 +val counter = UInt.until(LIMIT) <> VAR +if (counter == LIMIT - 1) // Int <> CONST compared with UInt -- works directly + counter := 0 +``` +/// + +#### Bits Comparisons + +`Bits` values support `==` and `!=` with other `Bits` values of the same width, with `all(0)`, `all(1)`, or with sized literals (`d"..."`, `h"..."`, `b"..."`). Plain Scala `Int` literals cannot be compared directly with `Bits` -- use a sized literal or convert to `.uint` first: + +```scala +val b8 = Bits(8) <> VAR +val isAllOnes = b8 == all(1) // Boolean: all bits are 1 +val isAllZeros = b8 == all(0) // Boolean: all bits are 0 +val isMatch = b8 == h"B0" // Boolean: exact match with hex literal +val isDec = b8 == d"8'12" // Boolean: match with sized decimal + +// ERROR: An integer value cannot be a candidate for a Bits type. +// val bad = b8 == 0 +// FIX: use all(0), a sized literal, or convert to UInt first: +// b8 == all(0) OR b8 == d"8'0" OR b8.uint == 0 +``` + +#### Enum, Struct, and Tuple Comparisons + +Enums, structs, and tuples support equality comparisons (`==` and `!=`) between values of the same type: + +```scala +val e1 = MyEnum <> VAR +val e2 = MyEnum <> VAR +val eq = e1 == e2 // Boolean + +val s1 = MyStruct <> VAR +val s2 = MyStruct <> VAR +val eq2 = s1 == s2 // Boolean + +val t1 = (UInt(8), Bit) <> VAR +val t2 = (UInt(8), Bit) <> VAR +val eq3 = t1 == t2 // Boolean +``` + +### Shift Operations (`<<`, `>>`) {#shift-ops} + +Applies to: `Bits`, `UInt`, `SInt` + +/// html | div.operations +| Operation | Description | LHS/RHS Constraints | Returns | +| ------------ | ----------- | ------------------- | ------- | +| `lhs << rhs` | Left shift | LHS: `Bits`/`UInt`/`SInt`, RHS: unsigned or `Int` | Same type as LHS | +| `lhs >> rhs` | Right shift (logical for `Bits`/`UInt`, arithmetic for `SInt`) | LHS: `Bits`/`UInt`/`SInt`, RHS: unsigned or `Int` | Same type as LHS | +/// + +The `>>` operator is **type-aware**: on `UInt` and `Bits` it performs a logical (zero-filling) right shift, and on `SInt` it performs an arithmetic (sign-extending) right shift. There is no separate `>>>` operator in DFHDL -- the operand type determines the behavior. + +```scala +val b = Bits(8) <> VAR +val u = UInt(8) <> VAR +val s = SInt(8) <> VAR + +val b_shifted = b << 2 // logical left shift +val u_shifted = u >> 2 // logical right shift (zero-fills MSBs) +val s_shifted = s >> 2 // arithmetic right shift (sign-extends MSBs) +``` + +### Max/Min Operations (`max`, `min`) {#max-min-ops} + +Applies to: `Int`, `Double` + +/// html | div.operations +| Operation | Description | Returns | +| --------------- | ----------- | ------- | +| `lhs max rhs` | Maximum of two values | Same type | +| `lhs min rhs` | Minimum of two values | Same type | +/// + +```scala +val param: Int <> CONST = 2 +val t1 = 1 max param // Int <> CONST = 2 +val t2 = 1 min param // Int <> CONST = 1 + +val d1 = Double <> VAR +val d2 = Double <> VAR +val t3 = d1 max d2 // Double +val t4 = d1 min d2 // Double +``` + +### Physical Arithmetic (`Time`, `Freq`) {#physical-ops} + +Applies to: `Time`, `Freq` + +Physical types follow dimensional analysis rules. Operations between `Time` and `Freq` produce dimensionally correct results. + +/// html | div.operations +| Operation | LHS | RHS | Returns | +| --------- | --- | --- | ------- | +| `lhs + rhs` | `Time` | `Time` | `Time` | +| `lhs - rhs` | `Time` | `Time` | `Time` | +| `lhs * rhs` | `Time` | Number | `Time` | +| `lhs * rhs` | `Freq` | Number | `Freq` | +| `lhs * rhs` | `Time` | `Freq` | Number | +| `lhs * rhs` | `Freq` | `Time` | Number | +| `lhs / rhs` | `Time` | Number | `Time` | +| `lhs / rhs` | `Freq` | Number | `Freq` | +| `lhs / rhs` | `Time` | `Time` | Number | +| `lhs / rhs` | `Freq` | `Freq` | Number | +| `lhs / rhs` | Number | `Time` | `Freq` | +| `lhs / rhs` | Number | `Freq` | `Time` | +/// + +```scala +val period = 10.ns +val freq = 100.MHz + +// Scaling +val half_period = period / 2 // Time: 5 ns +val double_freq = freq * 2 // Freq: 200 MHz + +// Dimensional conversions +val cycles = period * freq // Number: 1.0 +val calc_freq = 1 / period // Freq: 100 MHz +val calc_period = 1 / freq // Time: 10 ns +``` + +/// admonition | Cycle-based waits in RT domains + type: tip +The `.cy` unit creates cycle-count values for use with `.wait` in register-transfer domains: +```scala +class Example extends RTDesign: + 5.cy.wait // wait 5 clock cycles +``` +/// + +### `Int` Parameter Operations (`**`, `clog2`) {#int-param-ops} + +Applies to: `Int` (constant parameters) + +These operations are available for Scala `Int` or DFHDL `Int <> CONST` values and are primarily used for compile-time calculations such as computing bit widths. + +/// html | div.operations +| Operation | Description | Returns | +| --------------- | ----------- | ------- | +| `lhs ** rhs` | Power (exponentiation) | `Int` | +| `clog2(value)` | Ceiling of log base 2 | `Int` | +/// + +```scala +val param: Int <> CONST = 2 +val t1 = 3 ** param // Int <> CONST = 9 +val t2 = 2 ** param // Int <> CONST = 4 +val w = clog2(256) // Int = 8 (bits needed to represent 0..255) +``` + +/// admonition | Avoid using `clog2` directly for widths + type: warning +A common anti-pattern is using `clog2` to declare the width of bit-accurate values: +```scala +// DON'T do this: +val addr = UInt(clog2(DEPTH)) <> VAR +val mask = Bits(clog2(SIZE)) <> VAR +``` +Instead, use the `.until` or `.to` constructors which handle this automatically and are more readable: +```scala +// DO this instead: +val addr = UInt.until(DEPTH) <> VAR // width = clog2(DEPTH) +val mask = Bits.until(SIZE) <> VAR // width = clog2(SIZE) +``` +See the [DFType Constructors][DFDecimal] and [Bits constructors][DFBits] sections for details on `.until` and `.to`. +/// + +/// admonition | Non-constant DFHDL `Int` values + type: note +Non-constant DFHDL `Int` values (e.g., `Int <> VAR`) are possible and support the same arithmetic operations (`+`, `-`, `*`, `/`, `%`). However, they are discouraged for synthesizable designs because they map to a fixed 32-bit signed representation -- use `SInt[32]` instead for explicit control over the hardware. For simulation purposes, non-constant `Int` values are acceptable as long as the 32-bit width limitation is understood. +/// + +/// admonition | Slicing bits from a DFHDL `Int` + type: note +To extract a partial bit range from a DFHDL `Int` value, first convert it to `Bits` using `.bits`, then apply the slice: `myInt.bits(hi, lo)`. This is a `.bits` conversion followed by `(hi, lo)` slicing. The `.bits` conversion is a DFHDL extension method available on DFHDL `Int <> CONST` values, not on plain Scala `Int`. +/// + +### History Operations {#history-ops} + +Applies to: `Bit` (`.rising`, `.falling`) + +These operations are supported under both RT and ED domains. Under RT domain, these operations are synthesizable expressions. + +/// html | div.operations +| Operation | Description | LHS Constraints | Returns | +| ------------ | --------------------------------|-----------------------|-----------------------| +| `lhs.rising` | True when a value changes from `0` to `1` | `Bit` DFHDL value | `Boolean` DFHDL value | +| `lhs.falling` | True when a value changes from `1` to `0` | `Bit` DFHDL value | `Boolean` DFHDL value | +/// + +/// tab | `ED` + +```scala +class Foo extends EDDesign: + val clk = Bit <> IN + + /* VHDL-style */ + process(clk): + if (clk.rising) + //some sequential logic + + /* Verilog-style */ + process(clk.rising): + //some sequential logic +``` +/// details | Transitioning from Verilog + type: verilog +Under the ED domain, the `x.rising` and `x.falling` operations are equivalent to the Verilog `posedge x` and `negedge x`, respectively. +/// + +/// details | Transitioning from VHDL + type: vhdl +Under the ED domain, the `x.rising` and `x.falling` operations are equivalent to the VHDL `rising_edge(x)` and `falling_edge(x)`, respectively. +/// +/// + +/// tab | `RT` +The following `RT` domain edge detection design: +```scala +class Detector extends RTDesign: + val i = Bit <> IN + val o = Bit <> OUT + o := i.rising +``` +is compiled down to the following `ED` design (depending on the clock and reset configurations): +```scala +class Detector extends EDDesign: + val clk = Bit <> IN + val i = Bit <> IN + val o = Bit <> OUT + val i_reg = Bit <> VAR init 1 + process(clk.rising): + i_reg :== i + o <> !i_reg && i +``` +The initial (reset) register value is `1`/`0` for `rising`/`falling` operations, respectively. +This inherently prevents triggering immediately after reset, without sampling at least two input clock cycles. + +Both Verilog and VHDL have no equivalent synthesizable shorthand syntax. +/// + +For more information see either the [design domains][design-domains] or [processes][processes] sections. + +### Constant Meta Operations {#const-meta-ops} + +Applies to: constant `Bit`/`Boolean` values + +These operations are activated during the [elaboration stage][elaboration] of the DFHDL compilation, and are only available for constant `Bit`/`Boolean` DFHDL values. +Their use case is for meta-programming purposes, to control the generated code without the knowledge of the DFHDL compiler (could be considered as pre-processing steps). + +/// html | div.operations +| Operation | Description | LHS Constraints | Returns | +| ------------ | ----------- | ------------------- | ------- | +| `lhs.toScalaBitNum` | Extracts the known elaboration Scala `BitNum`(`1 | 0`) value from a constant DFHDL `Bit`/`Boolean` value | Constant `Bit`/`Boolean` DFHDL value | Scala `BitNum` value | +| `lhs.toScalaBoolean` | Extracts the known elaboration Scala `Boolean` value from a constant DFHDL `Bit`/`Boolean` value | Constant `Bit`/`Boolean` DFHDL value | Scala `Boolean` value | +/// + +The following runnable example demonstrates how such meta operation affect the elaborated design. +The `Boolean` argument `arg` of a design `Foo` is used twice within the design: +first, in an `if` condition directly; and second, in an `if` condition after a Scala value extraction. +When referenced directly, the `if` is elaborated as-is, but when the `if` is applied on the extracted Scala value, +the `if` is completely removed and either the block inside the `if` is elaborated when the argument is true or completely removed if false. + +/// tab | `Foo` +```scala +class Foo( + val arg: Boolean <> CONST +) extends DFDesign: + val o = Bit <> OUT + if (!arg) o := 1 + if (arg.toScalaBoolean) o := 0 +``` +/// + + +/// tab | `Foo(true)` +```scala +class Foo( + val arg: Boolean <> CONST +) extends DFDesign: + val o = Bit <> OUT + if (!arg) o := 1 + o := 0 +``` +/// + +/// tab | `Foo(false)` +```scala +class Foo( + val arg: Boolean <> CONST +) extends DFDesign: + val o = Bit <> OUT + if (!arg) o := 1 +``` +/// + +/// details | Runnable example + type: dfhdl +```scastie +import dfhdl.* + +@top(false) class Foo( + val arg: Boolean <> CONST +) extends DFDesign: + val o = Bit <> OUT + if (!arg) o := 1 + if (arg.toScalaBoolean) o := 0 + +@main def main = + println("Foo(true) Elaboration:") + Foo(true).printCodeString + println("Foo(false) Elaboration:") + Foo(false).printCodeString +``` +/// + +### Vector Element Access {#vector-ops} + +Applies to: `Vector` + +```scala +val elem = vec(idx) // Read element at index +vec(idx) := newValue // Write element at index +``` + +/// html | div.operations +| Operation | Description | Returns | +| ------------ | ----------- | ------- | +| `vec(idx)` | Access element at index | Element type | +| `vec.elements` | Get all elements as Scala sequence | Seq[BaseType] | +| `vec.size` | Get vector dimension | Int | +/// + + diff --git a/docs/user-guide/type-system/type-conversion-dark.svg b/docs/user-guide/type-system/type-conversion-dark.svg new file mode 100644 index 000000000..a12db8b89 --- /dev/null +++ b/docs/user-guide/type-system/type-conversion-dark.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UInt + + + + + + + Bits + + + + + + + SInt + + + + + + + + Type T + + + + + Boolean + + + + + + + Bit + + + + + + + Int + + + + + .toUInt(w) + + + + + .bits + + + .bits + + + .toBits(w) + + + .uint + + + .sint + + + + + .toSInt(w) + + + + + .signed + + + .bits + + + .as(T) + + + .bits + + + .bit + + + .bool + + + .toInt + + + .toInt + + + diff --git a/docs/user-guide/type-system/type-conversion-light.svg b/docs/user-guide/type-system/type-conversion-light.svg new file mode 100644 index 000000000..3ce450256 --- /dev/null +++ b/docs/user-guide/type-system/type-conversion-light.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UInt + + + + + + + Bits + + + + + + + SInt + + + + + + + + Type T + + + + + Boolean + + + + + + + Bit + + + + + + + Int + + + + + .toUInt(w) + + + + + .bits + + + .bits + + + .toBits(w) + + + .uint + + + .sint + + + + + .toSInt(w) + + + + + .signed + + + .bits + + + .as(T) + + + .bits + + + .bit + + + .bool + + + .toInt + + + .toInt + + + diff --git a/docs/user-guide/type-system/type-conversion.graphml b/docs/user-guide/type-system/type-conversion.graphml new file mode 100644 index 000000000..3bf943b70 --- /dev/null +++ b/docs/user-guide/type-system/type-conversion.graphml @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UInt + + + + + + + + + + + Bits + + + + + + + + + + + SInt + + + + + + + + + + + + Type T + + + + + + + + + + + + + + Boolean + + + + + + + + + + + Bit + + + + + + + + + + + Int + + + + + + + + + + + + + .bits + + + + + + + + + + + + + .uint + + + + + + + + + + + + + .sint + + + + + + + + + + + + + .bits + + + + + + + + + + + + + .bits + + + + + + + + + + + + + .as(T) + + + + + + + + + + + + + + .bit + + + + + + + + + + + + + .bool + + + + + + + + + + + + + .toUInt(w) + + + + + + + + + + + + + .toSInt(w) + + + + + + + + + + + + .bits + + + + + + + + + + + + .toBits(w) + + + + + + + + + + + + + + + .signed + + + + + + + + + + + + + + .toInt + + + + + + + + + + + + + + .toInt + + + + + + + + diff --git a/internals/src/main/scala/dfhdl/internals/MetaContext.scala b/internals/src/main/scala/dfhdl/internals/MetaContext.scala index 24214e574..ad984d17a 100644 --- a/internals/src/main/scala/dfhdl/internals/MetaContext.scala +++ b/internals/src/main/scala/dfhdl/internals/MetaContext.scala @@ -9,6 +9,7 @@ final case class Position( lineEnd: Int, columnEnd: Int ) derives CanEqual: + def shortFilePath: String = file.split("\\\\").last override def toString: String = s"$file:$lineStart:$columnStart - $lineEnd:$columnEnd" def fileUnixPath: String = file.replaceAll("\\\\", "/") def isUnknown: Boolean = this == Position.unknown diff --git a/internals/src/main/scala/dfhdl/internals/helpers.scala b/internals/src/main/scala/dfhdl/internals/helpers.scala index 1a533ff53..c68a0c70b 100644 --- a/internals/src/main/scala/dfhdl/internals/helpers.scala +++ b/internals/src/main/scala/dfhdl/internals/helpers.scala @@ -343,10 +343,11 @@ extension (str: String) def forceWindowsToLinuxPath: String = str.replaceAll("""\\""", "/") def simplePattenToRegex: Regex = val updatedPattern = - "^" + str - .replace(".", "\\.") // Escape dots to match literal dots - .replace("*", ".*") // Replace * with .* - .replace("?", ".") // Replace ? with . + "^" + + str + .replace(".", "\\.") // Escape dots to match literal dots + .replace("*", ".*") // Replace * with .* + .replace("?", ".") // Replace ? with . + "$" updatedPattern.r end extension @@ -387,6 +388,9 @@ lazy val getShellCommand: Option[String] = end match end getShellCommand +lazy val sbtnIsRunning: Boolean = + sbtIsRunning && getShellCommand.exists(cmd => cmd.endsWith("--server")) + lazy val sbtShellIsRunning: Boolean = getShellCommand.exists(cmd => cmd.endsWith("xsbt.boot.Boot") || cmd.endsWith("sbt-launch.jar")) diff --git a/lib/src/main/scala/dfhdl/app/DFApp.scala b/lib/src/main/scala/dfhdl/app/DFApp.scala index 5046a2bbb..499a9f95a 100644 --- a/lib/src/main/scala/dfhdl/app/DFApp.scala +++ b/lib/src/main/scala/dfhdl/app/DFApp.scala @@ -6,7 +6,7 @@ import wvlet.log.{Logger, LogFormatter} import scala.collection.mutable import dfhdl.options.* import org.rogach.scallop.* -import dfhdl.internals.sbtShellIsRunning +import dfhdl.internals.{sbtShellIsRunning, sbtnIsRunning} import scala.util.chaining.scalaUtilChainingOps import java.time.Instant import dfhdl.compiler.stages.{StagedDesign, CompiledDesign} @@ -400,7 +400,7 @@ trait DFApp: ) parsedCommandLine.getExitCodeOption match case Some(code) => - if (!sbtShellIsRunning) sys.exit(code) + if (!sbtShellIsRunning && !sbtnIsRunning) sys.exit(code) case None => given CanEqual[ScallopConfBase, ScallopConfBase] = CanEqual.derived // update app options from command line @@ -423,6 +423,9 @@ trait DFApp: printDFHDLCode = mode.`print-compile`.toOption.get, printBackendCode = mode.`print-backend`.toOption.get ) + printerOptions = printerOptions.copy( + globalDefsFileName = mode.`global-defs-name`.toOption.get + ) case _ => // update linter options from command line parsedCommandLine.mode match diff --git a/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala b/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala index 4faf67413..d320d9683 100644 --- a/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala +++ b/lib/src/main/scala/dfhdl/app/ParsedCommandLine.scala @@ -13,6 +13,7 @@ class ParsedCommandLine( )(using eo: ElaborationOptions, co: CompilerOptions, + pto: PrinterOptions, lo: LinterOptions, so: SimulatorOptions, bo: BuilderOptions, @@ -81,6 +82,12 @@ class ParsedCommandLine( hidden = hidden, noshort = true ) + val `global-defs-name` = opt[String]( + descr = "override the global definitions file name (without suffix)", + default = Some(pto.globalDefsFileName), + noshort = true, + hidden = hidden + ) end CompileMode trait CommitMode extends CompileMode: this: ScallopConf & Mode => @@ -241,11 +248,14 @@ class ParsedCommandLine( end HelpMode private def usageText(options: String): String = - import dfhdl.internals.{sbtIsRunning, scala_cliIsRunning, sbtShellIsRunning} + import dfhdl.internals.{sbtIsRunning, scala_cliIsRunning, sbtShellIsRunning, sbtnIsRunning} if (scala_cliIsRunning) s"scala run . -M $topScalaPath -- $options" else if (sbtIsRunning) - if (sbtShellIsRunning && !scastieIsRunning) s"runMain $topScalaPath $options" - else s"""sbt "runMain $topScalaPath $options"""" + if ((sbtShellIsRunning || sbtnIsRunning) && !scastieIsRunning) + s"runMain $topScalaPath $options" + else + val sbtCommand = if (sbtnIsRunning) "sbtn" else "sbt" + s"""$sbtCommand "runMain $topScalaPath $options"""" else s" $options" banner(s"""Design Name: $designName\nUsage: ${usageText("[design-args] [options]")} """) diff --git a/lib/src/main/scala/dfhdl/tools/toolsCore/IcarusVerilog.scala b/lib/src/main/scala/dfhdl/tools/toolsCore/IcarusVerilog.scala index 919421971..18b20820d 100644 --- a/lib/src/main/scala/dfhdl/tools/toolsCore/IcarusVerilog.scala +++ b/lib/src/main/scala/dfhdl/tools/toolsCore/IcarusVerilog.scala @@ -66,6 +66,9 @@ object IcarusVerilog extends VerilogLinter, VerilogSimulator: // suppress the "cannot be synthesized" warning when in simulation if (line.contains("cannot be synthesized") && getSet.designDB.inSimulation) true + // workaround for https://github.com/steveicarus/iverilog/issues/255 + else if (line.contains("Case unique/unique0 qualities are ignored")) + true else false ) diff --git a/lib/src/main/scala/dfhdl/tools/toolsCore/QuartusPrime.scala b/lib/src/main/scala/dfhdl/tools/toolsCore/QuartusPrime.scala index e2f409ad7..3cbe79ca6 100644 --- a/lib/src/main/scala/dfhdl/tools/toolsCore/QuartusPrime.scala +++ b/lib/src/main/scala/dfhdl/tools/toolsCore/QuartusPrime.scala @@ -325,11 +325,11 @@ class QuartusPrimeIPPrinter(using val members = qsysIP.members(MemberView.Folded) val ipVersion = members.collectFirst { case param: DFVal.DesignParam if param.getName == "version" => - " " + param.dfValRef.get.getConstData.get.asInstanceOf[Option[String]].get + " " + param.getConstData.get.asInstanceOf[Option[String]].get }.getOrElse("") val ipParams = members.collect { case param: DFVal.DesignParam if param.getName != "version" => - s"set_instance_parameter_value $ipInstanceName {${param.getName}} {${param.dfValRef.get.getConstData.get.asInstanceOf[Option[Any]].get}}" + s"set_instance_parameter_value $ipInstanceName {${param.getName}} {${param.getConstData.get.asInstanceOf[Option[Any]].get}}" }.mkString("\n") val ipExports = members.collect { case port @ DclPort() => diff --git a/lib/src/main/scala/dfhdl/tools/toolsCore/Vivado.scala b/lib/src/main/scala/dfhdl/tools/toolsCore/Vivado.scala index 86d4fdc2c..a4ead792c 100644 --- a/lib/src/main/scala/dfhdl/tools/toolsCore/Vivado.scala +++ b/lib/src/main/scala/dfhdl/tools/toolsCore/Vivado.scala @@ -331,7 +331,7 @@ class VivadoIPPrinter(using val members = vivadoIP.members(MemberView.Folded) val ipVersion = members.collectFirst { case param: DFVal.DesignParam if param.getName == "version" => - val version = param.dfValRef.get.getConstData.get.asInstanceOf[Option[String]].get + val version = param.getConstData.get.asInstanceOf[Option[String]].get if (version.nonEmpty) Some(" -version " + version) else None }.flatten.getOrElse("") @@ -339,7 +339,7 @@ class VivadoIPPrinter(using // Each DFVal.DesignParam except "version" becomes a CONFIG. {value} line val ipConfigParams = members.collect { case param: DFVal.DesignParam if param.getName != "version" => - val value = param.dfValRef.get.getConstData.get.asInstanceOf[Option[Any]].get + val value = param.getConstData.get.asInstanceOf[Option[Any]].get s"CONFIG.${param.getName} {$value}" } val ipConfigBlock = diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker.sv b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker.sv index 0a122d928..b1180e8c8 100644 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker.sv +++ b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker.sv @@ -1,7 +1,6 @@ /* This is a led blinker */ `default_nettype none `timescale 1ns/1ps -`include "Blinker_defs.svh" module Blinker#( parameter int CLK_FREQ_KHz = 50000, diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker_defs.svh b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker_defs.svh deleted file mode 100644 index 5c11a419d..000000000 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.sv2009/hdl/Blinker_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef BLINKER_DEFS -`define BLINKER_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker.v b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker.v index de1d0356e..f7d2673d3 100644 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker.v +++ b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker.v @@ -1,7 +1,6 @@ /* This is a led blinker */ `default_nettype none `timescale 1ns/1ps -`include "Blinker_defs.vh" module Blinker#( parameter integer CLK_FREQ_KHz = 50000, @@ -13,7 +12,6 @@ module Blinker#( output reg led ); `include "dfhdl_defs.vh" - `include "Blinker_defs.vh" /* Half-count of the toggle for 50% duty cycle */ parameter integer HALF_PERIOD = (CLK_FREQ_KHz * 1000) / (LED_FREQ_Hz * 2); reg [clog2(HALF_PERIOD) - 1:0] cnt; diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker_defs.vh b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker_defs.vh deleted file mode 100644 index 44cc9875c..000000000 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v2001/hdl/Blinker_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef BLINKER_DEFS -`define BLINKER_DEFS -`endif -`ifndef BLINKER_DEFS_MODULE -`define BLINKER_DEFS_MODULE -`else -`undef BLINKER_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker.v b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker.v index c55460553..69e54c06d 100644 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker.v +++ b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker.v @@ -1,7 +1,6 @@ /* This is a led blinker */ `default_nettype none `timescale 1ns/1ps -`include "Blinker_defs.vh" module Blinker( clk, @@ -9,7 +8,6 @@ module Blinker( led ); `include "dfhdl_defs.vh" - `include "Blinker_defs.vh" parameter integer CLK_FREQ_KHz = 50000; parameter integer LED_FREQ_Hz = 1; /* Half-count of the toggle for 50% duty cycle */ diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker_defs.vh b/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker_defs.vh deleted file mode 100644 index 44cc9875c..000000000 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/verilog.v95/hdl/Blinker_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef BLINKER_DEFS -`define BLINKER_DEFS -`endif -`ifndef BLINKER_DEFS_MODULE -`define BLINKER_DEFS_MODULE -`else -`undef BLINKER_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker.vhd b/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker.vhd index 79dac3e62..8da5dd70c 100644 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker.vhd +++ b/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker.vhd @@ -3,7 +3,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.Blinker_pkg.all; entity Blinker is generic ( diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker_pkg.vhd b/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker_pkg.vhd deleted file mode 100644 index 9eef929ff..000000000 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v2008/hdl/Blinker_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package Blinker_pkg is -end package Blinker_pkg; - -package body Blinker_pkg is -end package body Blinker_pkg; diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker.vhd b/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker.vhd index cec07a688..f06111cec 100644 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker.vhd +++ b/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker.vhd @@ -3,7 +3,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.Blinker_pkg.all; entity Blinker is generic ( diff --git a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker_pkg.vhd b/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker_pkg.vhd deleted file mode 100644 index 9eef929ff..000000000 --- a/lib/src/test/resources/ref/docExamples.BlinkerSpec/vhdl.v93/hdl/Blinker_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package Blinker_pkg is -end package Blinker_pkg; - -package body Blinker_pkg is -end package body Blinker_pkg; diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter.sv b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter.sv index fb082b6f9..c5ccd846f 100644 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter.sv +++ b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "Counter_defs.svh" module Counter#(parameter int width = 8)( input wire logic clk, diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter_defs.svh b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter_defs.svh deleted file mode 100644 index eb369b141..000000000 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.sv2009/hdl/Counter_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef COUNTER_DEFS -`define COUNTER_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter.v b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter.v index 9c3c0e012..2eb7f222a 100644 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter.v +++ b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "Counter_defs.vh" module Counter#(parameter integer width = 8)( input wire clk, @@ -9,7 +8,6 @@ module Counter#(parameter integer width = 8)( output reg [width - 1:0] cnt ); `include "dfhdl_defs.vh" - `include "Counter_defs.vh" always @(posedge clk) begin if (rst == 1'b1) cnt <= `dPW(0, 1, width); diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter_defs.vh b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter_defs.vh deleted file mode 100644 index ee2ed1b95..000000000 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v2001/hdl/Counter_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef COUNTER_DEFS -`define COUNTER_DEFS -`endif -`ifndef COUNTER_DEFS_MODULE -`define COUNTER_DEFS_MODULE -`else -`undef COUNTER_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter.v b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter.v index dbcb73567..23514d912 100644 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter.v +++ b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "Counter_defs.vh" module Counter( clk, @@ -9,7 +8,6 @@ module Counter( cnt ); `include "dfhdl_defs.vh" - `include "Counter_defs.vh" parameter integer width = 8; input wire clk; input wire rst; diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter_defs.vh b/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter_defs.vh deleted file mode 100644 index ee2ed1b95..000000000 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/verilog.v95/hdl/Counter_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef COUNTER_DEFS -`define COUNTER_DEFS -`endif -`ifndef COUNTER_DEFS_MODULE -`define COUNTER_DEFS_MODULE -`else -`undef COUNTER_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter.vhd b/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter.vhd index 9572b7058..884b62e07 100644 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter.vhd +++ b/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.Counter_pkg.all; entity Counter is generic ( diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter_pkg.vhd b/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter_pkg.vhd deleted file mode 100644 index 74ad8b33e..000000000 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v2008/hdl/Counter_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package Counter_pkg is -end package Counter_pkg; - -package body Counter_pkg is -end package body Counter_pkg; diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter.vhd b/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter.vhd index ca06ff824..2921c2804 100644 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter.vhd +++ b/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.Counter_pkg.all; entity Counter is generic ( diff --git a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter_pkg.vhd b/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter_pkg.vhd deleted file mode 100644 index 74ad8b33e..000000000 --- a/lib/src/test/resources/ref/docExamples.CounterSpec/vhdl.v93/hdl/Counter_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package Counter_pkg is -end package Counter_pkg; - -package body Counter_pkg is -end package body Counter_pkg; diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1.sv b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1.sv index 73001a45a..0b6f43b99 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1.sv +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdder1_defs.svh" module FullAdder1( input wire logic a, diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1_defs.svh b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1_defs.svh deleted file mode 100644 index 83e5e9ff9..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.sv2009/hdl/FullAdder1_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef FULLADDER1_DEFS -`define FULLADDER1_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1.v b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1.v index dfa5fcd0f..4d3248626 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1.v +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdder1_defs.vh" module FullAdder1( input wire a, @@ -10,7 +9,6 @@ module FullAdder1( output wire c_out ); `include "dfhdl_defs.vh" - `include "FullAdder1_defs.vh" assign sum = (a ^ b) ^ c_in; assign c_out = ((a & b) | (b & c_in)) | (c_in & a); endmodule diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1_defs.vh b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1_defs.vh deleted file mode 100644 index 652b16c28..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v2001/hdl/FullAdder1_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef FULLADDER1_DEFS -`define FULLADDER1_DEFS -`endif -`ifndef FULLADDER1_DEFS_MODULE -`define FULLADDER1_DEFS_MODULE -`else -`undef FULLADDER1_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1.v b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1.v index 4255f1b54..d994fb3ff 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1.v +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdder1_defs.vh" module FullAdder1( a, @@ -10,7 +9,6 @@ module FullAdder1( c_out ); `include "dfhdl_defs.vh" - `include "FullAdder1_defs.vh" input wire a; input wire b; input wire c_in; diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1_defs.vh b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1_defs.vh deleted file mode 100644 index 652b16c28..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/verilog.v95/hdl/FullAdder1_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef FULLADDER1_DEFS -`define FULLADDER1_DEFS -`endif -`ifndef FULLADDER1_DEFS_MODULE -`define FULLADDER1_DEFS_MODULE -`else -`undef FULLADDER1_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1.vhd b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1.vhd index 5fb6396f8..e2be3df93 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.FullAdder1_pkg.all; entity FullAdder1 is port ( diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1_pkg.vhd b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1_pkg.vhd deleted file mode 100644 index 44a6d88c9..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v2008/hdl/FullAdder1_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package FullAdder1_pkg is -end package FullAdder1_pkg; - -package body FullAdder1_pkg is -end package body FullAdder1_pkg; diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1.vhd b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1.vhd index 5fb6396f8..e2be3df93 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.FullAdder1_pkg.all; entity FullAdder1 is port ( diff --git a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1_pkg.vhd b/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1_pkg.vhd deleted file mode 100644 index 44a6d88c9..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdder1Spec/vhdl.v93/hdl/FullAdder1_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package FullAdder1_pkg is -end package FullAdder1_pkg; - -package body FullAdder1_pkg is -end package body FullAdder1_pkg; diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdder1.sv b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdder1.sv index 7689a1359..0b6f43b99 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdder1.sv +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdder1.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdderN_defs.svh" module FullAdder1( input wire logic a, diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN.sv b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN.sv index 36f54e96c..97be2f5c3 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN.sv +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdderN_defs.svh" module FullAdderN( input wire logic [3:0] a, diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN_defs.svh b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN_defs.svh deleted file mode 100644 index 3cab3ebce..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.sv2009/hdl/FullAdderN_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef FULLADDERN_DEFS -`define FULLADDERN_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdder1.v b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdder1.v index 7149b5fd6..4d3248626 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdder1.v +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdder1.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdderN_defs.vh" module FullAdder1( input wire a, @@ -10,7 +9,6 @@ module FullAdder1( output wire c_out ); `include "dfhdl_defs.vh" - `include "FullAdderN_defs.vh" assign sum = (a ^ b) ^ c_in; assign c_out = ((a & b) | (b & c_in)) | (c_in & a); endmodule diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN.v b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN.v index 39cd161a8..23c85f734 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN.v +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdderN_defs.vh" module FullAdderN( input wire [3:0] a, @@ -10,7 +9,6 @@ module FullAdderN( output wire c_out ); `include "dfhdl_defs.vh" - `include "FullAdderN_defs.vh" wire adder_0_a; wire adder_0_b; wire adder_0_c_in; diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN_defs.vh b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN_defs.vh deleted file mode 100644 index 0ec185153..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v2001/hdl/FullAdderN_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef FULLADDERN_DEFS -`define FULLADDERN_DEFS -`endif -`ifndef FULLADDERN_DEFS_MODULE -`define FULLADDERN_DEFS_MODULE -`else -`undef FULLADDERN_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdder1.v b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdder1.v index 652b63bb0..d994fb3ff 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdder1.v +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdder1.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdderN_defs.vh" module FullAdder1( a, @@ -10,7 +9,6 @@ module FullAdder1( c_out ); `include "dfhdl_defs.vh" - `include "FullAdderN_defs.vh" input wire a; input wire b; input wire c_in; diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN.v b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN.v index 3fd4b2615..77588f115 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN.v +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "FullAdderN_defs.vh" module FullAdderN( a, @@ -10,7 +9,6 @@ module FullAdderN( c_out ); `include "dfhdl_defs.vh" - `include "FullAdderN_defs.vh" input wire [3:0] a; input wire [3:0] b; input wire c_in; diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN_defs.vh b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN_defs.vh deleted file mode 100644 index 0ec185153..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/verilog.v95/hdl/FullAdderN_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef FULLADDERN_DEFS -`define FULLADDERN_DEFS -`endif -`ifndef FULLADDERN_DEFS_MODULE -`define FULLADDERN_DEFS_MODULE -`else -`undef FULLADDERN_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdder1.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdder1.vhd index 3dc80d3de..e2be3df93 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdder1.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdder1.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.FullAdderN_pkg.all; entity FullAdder1 is port ( diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN.vhd index b0b2f60bf..65c615061 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.FullAdderN_pkg.all; entity FullAdderN is port ( diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN_pkg.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN_pkg.vhd deleted file mode 100644 index 2912cd37e..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v2008/hdl/FullAdderN_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package FullAdderN_pkg is -end package FullAdderN_pkg; - -package body FullAdderN_pkg is -end package body FullAdderN_pkg; diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdder1.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdder1.vhd index 3dc80d3de..e2be3df93 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdder1.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdder1.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.FullAdderN_pkg.all; entity FullAdder1 is port ( diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN.vhd index 56085de99..d6ac5d84d 100644 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN.vhd +++ b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.FullAdderN_pkg.all; entity FullAdderN is port ( diff --git a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN_pkg.vhd b/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN_pkg.vhd deleted file mode 100644 index 2912cd37e..000000000 --- a/lib/src/test/resources/ref/docExamples.FullAdderNSpec/vhdl.v93/hdl/FullAdderN_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package FullAdderN_pkg is -end package FullAdderN_pkg; - -package body FullAdderN_pkg is -end package body FullAdderN_pkg; diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile.sv b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile.sv index 32d8138c0..991b74167 100644 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile.sv +++ b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "RegFile_defs.svh" module RegFile#( parameter int DATA_WIDTH = 32, diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile_defs.svh b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile_defs.svh deleted file mode 100644 index 8505bc1e4..000000000 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.sv2009/hdl/RegFile_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef REGFILE_DEFS -`define REGFILE_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile.v b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile.v index cef0a6c8e..81b50ae73 100644 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile.v +++ b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "RegFile_defs.vh" module RegFile#( parameter integer DATA_WIDTH = 32, @@ -16,7 +15,6 @@ module RegFile#( input wire rd_wren ); `include "dfhdl_defs.vh" - `include "RegFile_defs.vh" reg [DATA_WIDTH - 1:0] regs [0:REG_NUM - 1]; always @(posedge clk) begin diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile_defs.vh b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile_defs.vh deleted file mode 100644 index 1dcaad88b..000000000 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v2001/hdl/RegFile_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef REGFILE_DEFS -`define REGFILE_DEFS -`endif -`ifndef REGFILE_DEFS_MODULE -`define REGFILE_DEFS_MODULE -`else -`undef REGFILE_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile.v b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile.v index d21b85bca..222e49e9b 100644 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile.v +++ b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "RegFile_defs.vh" module RegFile( clk, @@ -13,7 +12,6 @@ module RegFile( rd_wren ); `include "dfhdl_defs.vh" - `include "RegFile_defs.vh" parameter integer DATA_WIDTH = 32; parameter integer REG_NUM = 32; input wire clk; diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile_defs.vh b/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile_defs.vh deleted file mode 100644 index 1dcaad88b..000000000 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/verilog.v95/hdl/RegFile_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef REGFILE_DEFS -`define REGFILE_DEFS -`endif -`ifndef REGFILE_DEFS_MODULE -`define REGFILE_DEFS_MODULE -`else -`undef REGFILE_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile.vhd b/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile.vhd index b9ad459b4..df5c33443 100644 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile.vhd +++ b/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.RegFile_pkg.all; entity RegFile is generic ( diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile_pkg.vhd b/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile_pkg.vhd deleted file mode 100644 index 25ee852ce..000000000 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v2008/hdl/RegFile_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package RegFile_pkg is -end package RegFile_pkg; - -package body RegFile_pkg is -end package body RegFile_pkg; diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile.vhd b/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile.vhd index df815655b..de8f519da 100644 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile.vhd +++ b/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.RegFile_pkg.all; entity RegFile is generic ( diff --git a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile_pkg.vhd b/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile_pkg.vhd deleted file mode 100644 index 25ee852ce..000000000 --- a/lib/src/test/resources/ref/docExamples.RegFileSpec/vhdl.v93/hdl/RegFile_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package RegFile_pkg is -end package RegFile_pkg; - -package body RegFile_pkg is -end package body RegFile_pkg; diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR.sv b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR.sv index d141c2eac..7909a4580 100644 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR.sv +++ b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "TrueDPR_defs.svh" module TrueDPR#( parameter int DATA_WIDTH = 8, diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR_defs.svh b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR_defs.svh deleted file mode 100644 index 19c985635..000000000 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.sv2009/hdl/TrueDPR_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef TRUEDPR_DEFS -`define TRUEDPR_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR.v b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR.v index 8f11982e7..200600c9c 100644 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR.v +++ b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "TrueDPR_defs.vh" module TrueDPR#( parameter integer DATA_WIDTH = 8, @@ -18,7 +17,6 @@ module TrueDPR#( input wire b_we ); `include "dfhdl_defs.vh" - `include "TrueDPR_defs.vh" reg [DATA_WIDTH - 1:0] ram [0:(2 ** ADDR_WIDTH) - 1]; always @(posedge a_clk) begin diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR_defs.vh b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR_defs.vh deleted file mode 100644 index 1719dbb00..000000000 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v2001/hdl/TrueDPR_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef TRUEDPR_DEFS -`define TRUEDPR_DEFS -`endif -`ifndef TRUEDPR_DEFS_MODULE -`define TRUEDPR_DEFS_MODULE -`else -`undef TRUEDPR_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR.v b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR.v index cc52f0d25..db25bf457 100644 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR.v +++ b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "TrueDPR_defs.vh" module TrueDPR( a_clk, @@ -15,7 +14,6 @@ module TrueDPR( b_we ); `include "dfhdl_defs.vh" - `include "TrueDPR_defs.vh" parameter integer DATA_WIDTH = 8; parameter integer ADDR_WIDTH = 8; input wire a_clk; diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR_defs.vh b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR_defs.vh deleted file mode 100644 index 1719dbb00..000000000 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/verilog.v95/hdl/TrueDPR_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef TRUEDPR_DEFS -`define TRUEDPR_DEFS -`endif -`ifndef TRUEDPR_DEFS_MODULE -`define TRUEDPR_DEFS_MODULE -`else -`undef TRUEDPR_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR.vhd b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR.vhd index 643f0a6c4..2360f9c9e 100644 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR.vhd +++ b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.TrueDPR_pkg.all; entity TrueDPR is generic ( diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR_pkg.vhd b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR_pkg.vhd deleted file mode 100644 index edd333ecd..000000000 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v2008/hdl/TrueDPR_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package TrueDPR_pkg is -end package TrueDPR_pkg; - -package body TrueDPR_pkg is -end package body TrueDPR_pkg; diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR.vhd b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR.vhd index 5bfabb3a6..47592fe69 100644 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR.vhd +++ b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.TrueDPR_pkg.all; entity TrueDPR is generic ( diff --git a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR_pkg.vhd b/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR_pkg.vhd deleted file mode 100644 index edd333ecd..000000000 --- a/lib/src/test/resources/ref/docExamples.TrueDPRSpec/vhdl.v93/hdl/TrueDPR_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package TrueDPR_pkg is -end package TrueDPR_pkg; - -package body TrueDPR_pkg is -end package body TrueDPR_pkg; diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx.sv b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx.sv index 1704e8e35..936cd1413 100644 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx.sv +++ b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx.sv @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "UART_Tx_defs.svh" module UART_Tx#( parameter int CLK_FREQ_KHz = 50000, @@ -16,7 +15,7 @@ module UART_Tx#( ); `include "dfhdl_defs.svh" localparam int BIT_CLOCKS = (CLK_FREQ_KHz * 1000) / BAUD_RATE_BPS; - typedef enum { + typedef enum logic [4:0] { Status_Idle = 1, Status_StartBit = 2, Status_DataBits = 4, @@ -35,7 +34,7 @@ module UART_Tx#( dataBitCnt <= 3'd0; end else begin - case (status) + unique case (status) Status_Idle: begin tx_en <= 1'b0; tx <= 1'b1; diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx_defs.svh b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx_defs.svh deleted file mode 100644 index ef4db63a6..000000000 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.sv2009/hdl/UART_Tx_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef UART_TX_DEFS -`define UART_TX_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx.v b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx.v index c07e7d415..875dff3c9 100644 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx.v +++ b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "UART_Tx_defs.vh" module UART_Tx#( parameter integer CLK_FREQ_KHz = 50000, @@ -15,7 +14,6 @@ module UART_Tx#( output reg tx_done ); `include "dfhdl_defs.vh" - `include "UART_Tx_defs.vh" parameter integer BIT_CLOCKS = (CLK_FREQ_KHz * 1000) / BAUD_RATE_BPS; `define Status_Idle 1 `define Status_StartBit 2 diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx_defs.vh b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx_defs.vh deleted file mode 100644 index 186b0a671..000000000 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v2001/hdl/UART_Tx_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef UART_TX_DEFS -`define UART_TX_DEFS -`endif -`ifndef UART_TX_DEFS_MODULE -`define UART_TX_DEFS_MODULE -`else -`undef UART_TX_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx.v b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx.v index df71b3854..13a9d8a07 100644 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx.v +++ b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx.v @@ -1,6 +1,5 @@ `default_nettype none `timescale 1ns/1ps -`include "UART_Tx_defs.vh" module UART_Tx( clk, @@ -12,7 +11,6 @@ module UART_Tx( tx_done ); `include "dfhdl_defs.vh" - `include "UART_Tx_defs.vh" parameter integer CLK_FREQ_KHz = 50000; parameter integer BAUD_RATE_BPS = 115200; parameter integer BIT_CLOCKS = (CLK_FREQ_KHz * 1000) / BAUD_RATE_BPS; diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx_defs.vh b/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx_defs.vh deleted file mode 100644 index 186b0a671..000000000 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/verilog.v95/hdl/UART_Tx_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef UART_TX_DEFS -`define UART_TX_DEFS -`endif -`ifndef UART_TX_DEFS_MODULE -`define UART_TX_DEFS_MODULE -`else -`undef UART_TX_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx.vhd b/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx.vhd index 80ac54ec1..5c8dd2df1 100644 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx.vhd +++ b/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.UART_Tx_pkg.all; entity UART_Tx is generic ( diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx_pkg.vhd b/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx_pkg.vhd deleted file mode 100644 index 487a30013..000000000 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v2008/hdl/UART_Tx_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package UART_Tx_pkg is -end package UART_Tx_pkg; - -package body UART_Tx_pkg is -end package body UART_Tx_pkg; diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx.vhd b/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx.vhd index 15e99debc..05ccdb87b 100644 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx.vhd +++ b/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx.vhd @@ -2,7 +2,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.UART_Tx_pkg.all; entity UART_Tx is generic ( diff --git a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx_pkg.vhd b/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx_pkg.vhd deleted file mode 100644 index 487a30013..000000000 --- a/lib/src/test/resources/ref/docExamples.UART_TxSpec/vhdl.v93/hdl/UART_Tx_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package UART_Tx_pkg is -end package UART_Tx_pkg; - -package body UART_Tx_pkg is -end package body UART_Tx_pkg; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2.sv b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2.sv index a7b381e2e..319d630ea 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2.sv +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2.sv @@ -1,7 +1,6 @@ /* A two-bits left shifter */ `default_nettype none `timescale 1ns/1ps -`include "LeftShift2_defs.svh" module LeftShift2( /* bits input */ diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2_defs.svh b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2_defs.svh deleted file mode 100644 index 7c012faf5..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.sv2009/hdl/LeftShift2_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef LEFTSHIFT2_DEFS -`define LEFTSHIFT2_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2.v b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2.v index c8454b4a6..e5af17844 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2.v +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2.v @@ -1,7 +1,6 @@ /* A two-bits left shifter */ `default_nettype none `timescale 1ns/1ps -`include "LeftShift2_defs.vh" module LeftShift2( /* bits input */ @@ -10,6 +9,5 @@ module LeftShift2( output wire [7:0] oBits ); `include "dfhdl_defs.vh" - `include "LeftShift2_defs.vh" assign oBits = iBits << 2; endmodule diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2_defs.vh b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2_defs.vh deleted file mode 100644 index 9dc730828..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v2001/hdl/LeftShift2_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef LEFTSHIFT2_DEFS -`define LEFTSHIFT2_DEFS -`endif -`ifndef LEFTSHIFT2_DEFS_MODULE -`define LEFTSHIFT2_DEFS_MODULE -`else -`undef LEFTSHIFT2_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2.v b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2.v index b4efff3a8..ed0b812b1 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2.v +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2.v @@ -1,14 +1,12 @@ /* A two-bits left shifter */ `default_nettype none `timescale 1ns/1ps -`include "LeftShift2_defs.vh" module LeftShift2( iBits, oBits ); `include "dfhdl_defs.vh" - `include "LeftShift2_defs.vh" /* bits input */ input wire [7:0] iBits; /* bits output */ diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2_defs.vh b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2_defs.vh deleted file mode 100644 index 9dc730828..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/verilog.v95/hdl/LeftShift2_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef LEFTSHIFT2_DEFS -`define LEFTSHIFT2_DEFS -`endif -`ifndef LEFTSHIFT2_DEFS_MODULE -`define LEFTSHIFT2_DEFS_MODULE -`else -`undef LEFTSHIFT2_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2.vhd index dafcd564d..b0c28bffd 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2.vhd @@ -3,7 +3,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.LeftShift2_pkg.all; entity LeftShift2 is port ( diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2_pkg.vhd deleted file mode 100644 index 98e980ea8..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v2008/hdl/LeftShift2_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package LeftShift2_pkg is -end package LeftShift2_pkg; - -package body LeftShift2_pkg is -end package body LeftShift2_pkg; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2.vhd index dafcd564d..b0c28bffd 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2.vhd @@ -3,7 +3,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.LeftShift2_pkg.all; entity LeftShift2 is port ( diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2_pkg.vhd deleted file mode 100644 index 98e980ea8..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo1.LeftShift2Spec/vhdl.v93/hdl/LeftShift2_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package LeftShift2_pkg is -end package LeftShift2_pkg; - -package body LeftShift2_pkg is -end package body LeftShift2_pkg; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic.sv b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic.sv index 0e47cdaa0..24e5c224f 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic.sv +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic.sv @@ -1,7 +1,6 @@ /* A basic left shifter */ `default_nettype none `timescale 1ns/1ps -`include "LeftShiftBasic_defs.svh" module LeftShiftBasic( /* bits input */ diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic_defs.svh b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic_defs.svh deleted file mode 100644 index c1d7b0a2c..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.sv2009/hdl/LeftShiftBasic_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef LEFTSHIFTBASIC_DEFS -`define LEFTSHIFTBASIC_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic.v b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic.v index dedfaef60..7a9c0da50 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic.v +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic.v @@ -1,7 +1,6 @@ /* A basic left shifter */ `default_nettype none `timescale 1ns/1ps -`include "LeftShiftBasic_defs.vh" module LeftShiftBasic( /* bits input */ @@ -12,6 +11,5 @@ module LeftShiftBasic( output wire [7:0] oBits ); `include "dfhdl_defs.vh" - `include "LeftShiftBasic_defs.vh" assign oBits = iBits << shift; endmodule diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic_defs.vh b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic_defs.vh deleted file mode 100644 index 6cb4101a1..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v2001/hdl/LeftShiftBasic_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef LEFTSHIFTBASIC_DEFS -`define LEFTSHIFTBASIC_DEFS -`endif -`ifndef LEFTSHIFTBASIC_DEFS_MODULE -`define LEFTSHIFTBASIC_DEFS_MODULE -`else -`undef LEFTSHIFTBASIC_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic.v b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic.v index 0b0d55fee..2526d4263 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic.v +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic.v @@ -1,7 +1,6 @@ /* A basic left shifter */ `default_nettype none `timescale 1ns/1ps -`include "LeftShiftBasic_defs.vh" module LeftShiftBasic( iBits, @@ -9,7 +8,6 @@ module LeftShiftBasic( oBits ); `include "dfhdl_defs.vh" - `include "LeftShiftBasic_defs.vh" /* bits input */ input wire [7:0] iBits; /* requested shift */ diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic_defs.vh b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic_defs.vh deleted file mode 100644 index 6cb4101a1..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/verilog.v95/hdl/LeftShiftBasic_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef LEFTSHIFTBASIC_DEFS -`define LEFTSHIFTBASIC_DEFS -`endif -`ifndef LEFTSHIFTBASIC_DEFS_MODULE -`define LEFTSHIFTBASIC_DEFS_MODULE -`else -`undef LEFTSHIFTBASIC_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic.vhd index 1f7627399..ace9fd9ec 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic.vhd @@ -3,7 +3,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.LeftShiftBasic_pkg.all; entity LeftShiftBasic is port ( diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic_pkg.vhd deleted file mode 100644 index 4e02534fe..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v2008/hdl/LeftShiftBasic_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package LeftShiftBasic_pkg is -end package LeftShiftBasic_pkg; - -package body LeftShiftBasic_pkg is -end package body LeftShiftBasic_pkg; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic.vhd index 1f7627399..ace9fd9ec 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic.vhd @@ -3,7 +3,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.LeftShiftBasic_pkg.all; entity LeftShiftBasic is port ( diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic_pkg.vhd deleted file mode 100644 index 4e02534fe..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo2.LeftShiftBasicSpec/vhdl.v93/hdl/LeftShiftBasic_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package LeftShiftBasic_pkg is -end package LeftShiftBasic_pkg; - -package body LeftShiftBasic_pkg is -end package body LeftShiftBasic_pkg; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen.sv b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen.sv index fad9cc77b..8aa8b2510 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen.sv +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen.sv @@ -5,7 +5,6 @@ */ `default_nettype none `timescale 1ns/1ps -`include "LeftShiftGen_defs.svh" module LeftShiftGen#(parameter int width = 8)( /* bits input */ diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen_defs.svh b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen_defs.svh deleted file mode 100644 index 458c38ad4..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.sv2009/hdl/LeftShiftGen_defs.svh +++ /dev/null @@ -1,3 +0,0 @@ -`ifndef LEFTSHIFTGEN_DEFS -`define LEFTSHIFTGEN_DEFS -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen.v b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen.v index 9c6b302d4..380beb54a 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen.v +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen.v @@ -5,7 +5,6 @@ */ `default_nettype none `timescale 1ns/1ps -`include "LeftShiftGen_defs.vh" module LeftShiftGen#(parameter integer width = 8)( /* bits input */ @@ -16,6 +15,5 @@ module LeftShiftGen#(parameter integer width = 8)( output wire [width - 1:0] oBits ); `include "dfhdl_defs.vh" - `include "LeftShiftGen_defs.vh" assign oBits = iBits << shift; endmodule diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen_defs.vh b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen_defs.vh deleted file mode 100644 index a0dcc749d..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v2001/hdl/LeftShiftGen_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef LEFTSHIFTGEN_DEFS -`define LEFTSHIFTGEN_DEFS -`endif -`ifndef LEFTSHIFTGEN_DEFS_MODULE -`define LEFTSHIFTGEN_DEFS_MODULE -`else -`undef LEFTSHIFTGEN_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen.v b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen.v index 4e9ef9368..0b268a7ef 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen.v +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen.v @@ -5,7 +5,6 @@ */ `default_nettype none `timescale 1ns/1ps -`include "LeftShiftGen_defs.vh" module LeftShiftGen( iBits, @@ -13,7 +12,6 @@ module LeftShiftGen( oBits ); `include "dfhdl_defs.vh" - `include "LeftShiftGen_defs.vh" parameter integer width = 8; /* bits input */ input wire [width - 1:0] iBits; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen_defs.vh b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen_defs.vh deleted file mode 100644 index a0dcc749d..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/verilog.v95/hdl/LeftShiftGen_defs.vh +++ /dev/null @@ -1,8 +0,0 @@ -`ifndef LEFTSHIFTGEN_DEFS -`define LEFTSHIFTGEN_DEFS -`endif -`ifndef LEFTSHIFTGEN_DEFS_MODULE -`define LEFTSHIFTGEN_DEFS_MODULE -`else -`undef LEFTSHIFTGEN_DEFS_MODULE -`endif diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen.vhd index 92a5b0696..eec45aff2 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen.vhd @@ -6,7 +6,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.LeftShiftGen_pkg.all; entity LeftShiftGen is generic ( diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen_pkg.vhd deleted file mode 100644 index bffa2421d..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v2008/hdl/LeftShiftGen_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package LeftShiftGen_pkg is -end package LeftShiftGen_pkg; - -package body LeftShiftGen_pkg is -end package body LeftShiftGen_pkg; diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen.vhd index 92a5b0696..eec45aff2 100644 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen.vhd +++ b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen.vhd @@ -6,7 +6,6 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.dfhdl_pkg.all; -use work.LeftShiftGen_pkg.all; entity LeftShiftGen is generic ( diff --git a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen_pkg.vhd b/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen_pkg.vhd deleted file mode 100644 index bffa2421d..000000000 --- a/lib/src/test/resources/ref/docExamples.ugdemos.demo3.LeftShiftGenSpec/vhdl.v93/hdl/LeftShiftGen_pkg.vhd +++ /dev/null @@ -1,10 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; -use work.dfhdl_pkg.all; - -package LeftShiftGen_pkg is -end package LeftShiftGen_pkg; - -package body LeftShiftGen_pkg is -end package body LeftShiftGen_pkg; diff --git a/lib/src/test/scala/issues/IssueSpec.scala b/lib/src/test/scala/issues/IssueSpec.scala index 1becff9d1..dce572d81 100644 --- a/lib/src/test/scala/issues/IssueSpec.scala +++ b/lib/src/test/scala/issues/IssueSpec.scala @@ -23,7 +23,6 @@ class IssuesSpec extends FunSuite: |use ieee.std_logic_1164.all; |use ieee.numeric_std.all; |use work.dfhdl_pkg.all; - |use work.ArrayIssue_pkg.all; | |entity ArrayIssue is |port ( @@ -61,7 +60,6 @@ class IssuesSpec extends FunSuite: i135.VerilogSRA().getCompiledCodeString, """|`default_nettype none |`timescale 1ns/1ps - |`include "VerilogSRA_defs.svh" | |module VerilogSRA( | input wire logic signed [9:0] a diff --git a/plugin/src/main/scala/plugin/CommonPhase.scala b/plugin/src/main/scala/plugin/CommonPhase.scala index 2255f8647..f121d004b 100755 --- a/plugin/src/main/scala/plugin/CommonPhase.scala +++ b/plugin/src/main/scala/plugin/CommonPhase.scala @@ -311,11 +311,19 @@ abstract class CommonPhase extends PluginPhase: case _ => None end extension + type PosKey = String + extension (sym: Symbol)(using Context) + def posKey: PosKey = sym.srcPos.show + extension (tree: Tree)(using Context) + def posKey: PosKey = tree.srcPos.show + extension (srcPos: util.SrcPos)(using Context) def show: String = - val pos = srcPos.startPos - val endPos = srcPos.endPos - s"${pos.source.path}:${pos.line}:${pos.column}-${endPos.line}:${endPos.column}" + if (srcPos.span == util.Spans.NoSpan) "NoSpan" + else + val pos = srcPos.startPos + val endPos = srcPos.endPos + s"${pos.source.path}:${pos.line}:${pos.column}-${endPos.line}:${endPos.column}" extension (tree: Apply)(using Context) def replaceArg(fromArg: Tree, toArg: Tree): Apply = diff --git a/plugin/src/main/scala/plugin/CustomControlPhase.scala b/plugin/src/main/scala/plugin/CustomControlPhase.scala index 9b1515ff9..060f35c34 100644 --- a/plugin/src/main/scala/plugin/CustomControlPhase.scala +++ b/plugin/src/main/scala/plugin/CustomControlPhase.scala @@ -38,8 +38,8 @@ class CustomControlPhase(setting: Setting) extends CommonPhase: // override val debugFilter: String => Boolean = _.contains("Playground.scala") override val runsAfter = Set(transform.Pickler.name) override val runsBefore = Set("MetaContextGen") - val ignoreIfs = mutable.Set.empty[String] - val replaceIfs = mutable.Set.empty[String] + val ignoreIfs = mutable.Set.empty[PosKey] + val replaceIfs = mutable.Set.empty[PosKey] var fromBooleanSym: Symbol = uninitialized var toFunc1Sym: Symbol = uninitialized var fromBranchesSym: Symbol = uninitialized @@ -99,16 +99,16 @@ class CustomControlPhase(setting: Setting) extends CommonPhase: case _ => false @tailrec private def ignoreElseIfRecur(tree: If)(using Context): Unit = - ignoreIfs += tree.srcPos.show + ignoreIfs += tree.posKey tree.elsep match case tree: If => ignoreElseIfRecur(tree) case _ => // done override def prepareForIf(tree: If)(using Context): Context = - if (!ignoreIfs.contains(tree.srcPos.show) && isHackedIfRecur(tree)) + if (!ignoreIfs.contains(tree.posKey) && isHackedIfRecur(tree)) tree.elsep match case tree: If => ignoreElseIfRecur(tree) case _ => // do nothing - replaceIfs += tree.srcPos.show + replaceIfs += tree.posKey ctx private def transformIfCond(condTree: Tree, dfcTree: Tree)(using @@ -167,7 +167,7 @@ class CustomControlPhase(setting: Setting) extends CommonPhase: case _ => false override def transformIf(tree: If)(using Context): Tree = - if (replaceIfs.contains(tree.srcPos.show)) + if (replaceIfs.contains(tree.posKey)) // debug("=======================") val dfcTree = dfcStack.head val combinedTpe = tree.tpe.widen @@ -780,7 +780,8 @@ class CustomControlPhase(setting: Setting) extends CommonPhase: } if (idxHigh != -1) report.error( - s"""Cannot compare a value of ${selectorWidth} bits width (LHS) to a value of ${selectorWidth - idxHigh - 1} bits width (RHS). + s"""Cannot compare a value of ${selectorWidth} bits width (LHS) to a value of ${selectorWidth - + idxHigh - 1} bits width (RHS). |An explicit conversion must be applied.""".stripMargin, patternTree.srcPos ) diff --git a/plugin/src/main/scala/plugin/LoopFSMPhase.scala b/plugin/src/main/scala/plugin/LoopFSMPhase.scala index 0cdacfde7..7c2b49d65 100644 --- a/plugin/src/main/scala/plugin/LoopFSMPhase.scala +++ b/plugin/src/main/scala/plugin/LoopFSMPhase.scala @@ -36,10 +36,11 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: var processAnonDefSym: Symbol = uninitialized var processScopeCtxSym: Symbol = uninitialized var stepType: Type = uninitialized - var pluginOnEntryExitSym: Symbol = uninitialized + var fallThroughType: Type = uninitialized + var pluginOnEntryExitFallThroughSym: Symbol = uninitialized var waitSym: Symbol = uninitialized var dfcStack: List[Tree] = Nil - val processStepDefs = mutable.LinkedHashMap.empty[Symbol, DefDef] + val processStepDefs = mutable.LinkedHashMap.empty[PosKey, DefDef] override val runsAfter = Set(transform.Pickler.name) override val runsBefore = Set("CustomControl") @@ -53,7 +54,7 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: override def transformDefDef(tree: DefDef)(using Context): Tree = val updatedDefDef = if (tree.symbol == processAnonDefSym) - val registeredSteps = processStepDefs.view.map { (sym, dd) => + val registeredSteps = processStepDefs.valuesIterator.map { dd => ref(registerStepSym) .appliedTo(dd.genMeta) .appliedTo(dfcStack.head, ref(processScopeCtxSym)) @@ -85,12 +86,14 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: report.error("Process blocks must only declare step `def`s or no step`def`s at all.", srcPos) enum CheckType derives CanEqual: - case None, Return, Loop, OnEntryExit + case None, Return, Loop, OnEntryExitFallThrough def processStatCheck(tree: Tree, returnCheck: CheckType)(using Context): Unit = tree match + case vd: ValDef if vd.tpt.tpe =:= defn.UnitType => + processStatCheck(vd.rhs, returnCheck) case Block(stats, expr) => returnCheck match - case CheckType.OnEntryExit => + case CheckType.OnEntryExitFallThrough => (expr :: stats).foreach(t => processStatCheck(t, returnCheck)) case _ => processStatCheck(stats, tree.srcPos) @@ -98,8 +101,8 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: case Literal(Constant(_: Unit)) => case _ => stats.headOption match - case Some(OnEntryDef() | OnExitDef()) => - case Some(dd: DefDef) => + case Some(OnEntryDef() | OnExitDef() | FallThroughDef()) => + case Some(dd: DefDef) => // allDefsErrMsg(expr.srcPos) case _ => processStatCheck(expr, returnCheck) @@ -130,13 +133,16 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: // |For a purely combinational loop, call `0.cy.wait` at the end of the loop block.""".stripMargin, // tree.srcPos // ) - case CheckType.OnEntryExit => + case CheckType.OnEntryExitFallThrough => tree match case dd: DefDef => - report.error("onEntry/onExit must not contain any other `def`s.", dd.srcPos) + report.error( + "onEntry/onExit/fallThrough must not contain any other `def`s.", + dd.srcPos + ) case Goto() | Wait() => report.error( - "onEntry/onExit `def`s cannot have `wait` or step goto statements.", + "onEntry/onExit/fallThrough `def`s cannot have `wait` or step goto statements.", tree.srcPos ) case _ => @@ -151,23 +157,22 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: case _: DefDef => true case _ => false }).runtimeChecked - val (onEntryExit: List[DefDef], stepDefs: List[DefDef]) = allDefs.partition { - case OnEntryDef() => true - case OnExitDef() => true - case _ => false + val (onEntryExitFallThrough: List[DefDef], stepDefs: List[DefDef]) = allDefs.partition { + case OnEntryDef() | OnExitDef() | FallThroughDef() => true + case _ => false } // if (stepDefs.nonEmpty && allStepBlocks.nonEmpty) allDefsErrMsg(srcPos) // checking onEntry and onExit defs syntax - onEntryExit.foreach { dd => + onEntryExitFallThrough.foreach { dd => if (dd.paramss != Nil) - report.error(s"An ${dd.name} def must not have arguments.", dd.srcPos) + report.error(s"`def ${dd.name}` must not have arguments.", dd.srcPos) } var errFound = false // checking process defs syntax and caching the process def symbols stepDefs.foreach { case dd @ DefDef(_, Nil, retTypeTree, _) if retTypeTree.tpe =:= stepType => - processStepDefs += (dd.symbol -> dd) + processStepDefs += (dd.symbol.posKey -> dd) case dd => report.error( "Unexpected register-transfer (RT) process `def` syntax. Must be `def xyz: Step = ...`", @@ -177,7 +182,9 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: } if (!errFound) stepDefs.foreach { dd => processStatCheck(dd.rhs, returnCheck = CheckType.Return) } - onEntryExit.foreach { dd => processStatCheck(dd.rhs, returnCheck = CheckType.OnEntryExit) } + onEntryExitFallThrough.foreach { dd => + processStatCheck(dd.rhs, returnCheck = CheckType.OnEntryExitFallThrough) + } allStepBlocks.foreach { step => processStatCheck(step, returnCheck = CheckType.None) } end processStatCheck @@ -271,7 +278,8 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: List(domainTypeTree) ) if anonfun.toString.startsWith("$anonfun") && process.toString == "process" && - forever.toString == "forever" && domainTypeTree.tpe.widenDealias.typeSymbol.name.toString == "RT" => + forever.toString == "forever" && + domainTypeTree.tpe.widenDealias.typeSymbol.name.toString == "RT" => processAnonDefSym = dd.symbol processScopeCtxSym = scopeCtx.symbol Some(ProcessForever(scopeCtx, dd.rhs)) @@ -283,13 +291,6 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: tree match case ProcessForever(scopeCtx, block) => processStatCheck(block, returnCheck = CheckType.None) - processStepDefs.view.groupBy(_._2.name.toString).foreach { case (name, defs) => - if (defs.size > 1) - report.error( - s"Process step `def`s must be unique. Found multiple `def $name: Step = ...`s.", - defs.head._2.srcPos - ) - } case Apply(Select(This(_), wait), args) if wait.toString == "wait" => // DFHDL/Java wait if (!tree.tpe.isContextualMethod) // Java's wait wouldn't be contextual report.error( @@ -307,9 +308,19 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: object OnExitDef: def unapply(tree: DefDef)(using Context): Boolean = tree.name.toString == "onExit" + object FallThroughDef: + def unapply(tree: DefDef)(using Context): Boolean = + if (tree.name.toString == "fallThrough") + if (!(tree.tpt.tpe <:< fallThroughType)) + report.error( + s"`def fallThrough` must return a DFHDL Boolean or Bit value.", + tree.srcPos + ) + true + else false object Goto: def unapply(tree: Ident)(using Context): Boolean = - processStepDefs.contains(tree.symbol) + processStepDefs.contains(tree.symbol.posKey) object Wait: def unapply(tree: Apply)(using Context): Boolean = tree.tpe.typeSymbol.fullName.toString == "dfhdl.core.Wait$package$.Wait" @@ -317,7 +328,7 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: tree match case Goto() => ref(gotoStepSym) - .appliedTo(Literal(Constant(tree.name.toString))) + .appliedTo(Literal(Constant(getStepKey(tree)))) .appliedTo(dfcStack.head, ref(processScopeCtxSym)) case _ => tree @@ -346,15 +357,22 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: case _ => tree + // this is used to get a unique key for the step, + // because the step name may be repeated when declared internally in another step or loop. + // the line position makes sure we get a unique key for the step. + private def getStepKey(tree: DefDef | Ident)(using Context): String = + val sym = tree.symbol + s"${sym.name}_${sym.srcPos.startPos.line + 1}" + override def transformStats(trees: List[Tree])(using Context): List[Tree] = trees.map { - case dd: DefDef if processStepDefs.contains(dd.symbol) => + case dd: DefDef if processStepDefs.contains(dd.symbol.posKey) => ref(addStepSym) - .appliedTo(Literal(Constant(dd.name.toString))) + .appliedTo(Literal(Constant(getStepKey(dd)))) .appliedTo(dd.rhs.changeOwner(dd.symbol, ctx.owner)) .appliedTo(dfcStack.head, ref(processScopeCtxSym)) - case dd @ (OnEntryDef() | OnExitDef()) => - ref(pluginOnEntryExitSym) + case dd @ (OnEntryDef() | OnExitDef() | FallThroughDef()) => + ref(pluginOnEntryExitFallThroughSym) .appliedTo(dd.genMeta) .appliedTo(dd.rhs.changeOwner(dd.symbol, ctx.owner)) .appliedTo(dfcStack.head) @@ -372,7 +390,16 @@ class LoopFSMPhase(setting: Setting) extends CommonPhase: fromBooleanSym = requiredMethod("dfhdl.core.r__For_Plugin.fromBoolean") customWhileSym = requiredMethod("dfhdl.core.DFWhile.plugin") stepType = requiredClassRef("dfhdl.core.Step") - pluginOnEntryExitSym = requiredMethod("dfhdl.core.Step.pluginOnEntryExit") + val dfTypeType = requiredClassRef("dfhdl.core.DFType") + val noArgsType = requiredClassRef("dfhdl.core.NoArgs") + val dfBoolOrBitType = + dfTypeType.appliedTo(requiredClassRef("dfhdl.compiler.ir.DFBoolOrBit"), noArgsType) + val modifierType = requiredClassRef("dfhdl.core.Modifier") + val modifierAnyType = + modifierType.appliedTo(List(defn.AnyType, defn.AnyType, defn.AnyType, defn.AnyType)) + val dfValType = requiredClassRef("dfhdl.core.DFVal") + fallThroughType = dfValType.appliedTo(dfBoolOrBitType, modifierAnyType) + pluginOnEntryExitFallThroughSym = requiredMethod("dfhdl.core.Step.pluginOnEntryExitFallThrough") processStepDefs.clear() ctx end prepareForUnit diff --git a/plugin/src/main/scala/plugin/MetaContextGenPhase.scala b/plugin/src/main/scala/plugin/MetaContextGenPhase.scala index 98f4e1c28..00d84d7c8 100755 --- a/plugin/src/main/scala/plugin/MetaContextGenPhase.scala +++ b/plugin/src/main/scala/plugin/MetaContextGenPhase.scala @@ -153,7 +153,8 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: val origApply = applyStack.head applyStack = applyStack.drop(1) if ( - fixedApply.tpe.isParameterless && !fixedApply.fun.symbol.ignoreMetaContext && !fixedApply.fun.symbol.forwardMetaContext + fixedApply.tpe.isParameterless && !fixedApply.fun.symbol.ignoreMetaContext && + !fixedApply.fun.symbol.forwardMetaContext ) fixedApply match // found a context argument @@ -390,8 +391,10 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: tree match case Apply(Select(lhs, fun), List(rhs)) if (fun == nme.EQ || fun == nme.NE) && - (lhs.tpe <:< defn.IntType || lhs.tpe <:< defn.BooleanType || lhs.tpe <:< defn - .TupleTypeRef) => + (lhs.tpe <:< defn.IntType || lhs.tpe <:< defn.BooleanType || + lhs.tpe <:< + defn + .TupleTypeRef) => val rhsSym = rhs.tpe.dealias.typeSymbol if (rhsSym == dfValSym) report.error( @@ -399,7 +402,8 @@ class MetaContextGenPhase(setting: Setting) extends CommonPhase: pos ) case Apply(Select(lhs, fun), List(Apply(Apply(Ident(hackName), _), _))) - if (fun == nme.ZOR || fun == nme.ZAND || fun == nme.XOR) && hackName.toString == "BooleanHack" => + if (fun == nme.ZOR || fun == nme.ZAND || fun == nme.XOR) && + hackName.toString == "BooleanHack" => report.error( s"Unsupported Scala Boolean primitive at the LHS of `$fun` with a DFHDL value.\nConsider switching positions of the arguments.", pos diff --git a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala index ebfd04491..72ffa9038 100644 --- a/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala +++ b/plugin/src/main/scala/plugin/MetaContextPlacerPhase.scala @@ -27,6 +27,7 @@ import dotty.tools.dotc.ast.Trees.Alternative is instantiated regularly the instance is transformed into an anonymous class instance with the override, otherwise all is required is to add the additional override to an existing anonymous DFHDL class instance. + Additionally, it transforms basic val x = y to val x = dfhdl.core.r__For_Plugin.identVal(y) if y is a DFVal */ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: import tpd._ @@ -49,6 +50,7 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: var noTopAnnotIsRequired: TypeRef = uninitialized var listMapEmptySym: TermSymbol = uninitialized var listMapSym: TermSymbol = uninitialized + var dfhdlDFValIdentSym: TermSymbol = uninitialized val defaultParamMap = mutable.Map.empty[ClassSymbol, Map[Int, Tree]] override def prepareForTypeDef(tree: TypeDef)(using Context): Context = val sym = tree.symbol @@ -237,7 +239,8 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: case Apply(Select(New(Ident(n)), _), _) if n == StdNames.tpnme.ANON_CLASS => tree case _ if ( - tree.fun.symbol.isClassConstructor && tpe.isParameterless && !ctx.owner.isClassConstructor && + tree.fun.symbol.isClassConstructor && tpe.isParameterless && + !ctx.owner.isClassConstructor && !ctx.owner.isClassConstructor && tpe.typeConstructor <:< hasDFCTpe ) => val cls = newNormalizedClassSymbol( @@ -291,6 +294,29 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: cpy.Block(tree)(stats = List(updatedTypeDef), expr = tree.expr) case _ => tree + // transform basic val x = y to val x = dfhdl.core.r__For_Plugin.identVal(y) if y is a DFVal + override def transformValDef(tree: ValDef)(using Context): ValDef = + object DFValIdent: + def unapply(tree: Tree)(using Context): Option[Tree] = + tree match + case ident @ Ident(name) if !name.toString.contains("$") => Some(tree) + case Select(DFValIdent(_), _) => Some(tree) + case This(DFValIdent(_)) => Some(tree) + case _ => None + end DFValIdent + tree.rhs match + case DFValIdent(rhs) + if !tree.symbol.flags.is(InlineProxy) && tree.tpt.tpe.dfValTpeOpt.nonEmpty => + val dfc = dfcArgStack.headOption.getOrElse(ref(emptyNoEODFCSym)) + val updatedRHS = + ref(dfhdlDFValIdentSym) + .appliedToType(rhs.tpe.widen) + .appliedTo(rhs) + .appliedTo(dfc) + cpy.ValDef(tree)(rhs = updatedRHS) + case _ => tree + end match + end transformValDef override def prepareForUnit(tree: Tree)(using Context): Context = super.prepareForUnit(tree) @@ -306,6 +332,7 @@ class MetaContextPlacerPhase(setting: Setting) extends CommonPhase: noTopAnnotIsRequired = requiredClassRef("dfhdl.internals.NoTopAnnotIsRequired") listMapEmptySym = requiredMethod("scala.collection.immutable.ListMap.empty") listMapSym = requiredModule("scala.collection.immutable.ListMap") + dfhdlDFValIdentSym = requiredMethod("dfhdl.core.r__For_Plugin.identVal") dfcArgStack = Nil defaultParamMap.clear() ctx diff --git a/project/build.properties b/project/build.properties index c8cca6491..8430e165c 100755 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.12.1 \ No newline at end of file +sbt.version = 1.12.8 \ No newline at end of file