Skip to content

Commit

Permalink
[Seq] Add initial value to compreg (#7553)
Browse files Browse the repository at this point in the history
This PR extends compreg's powerOnValue operand to be able to capture more complicated initialization such as firreg's randomized initialization or DPI calls. This change should make register initialization more modular and a step forward towards #7213. 

While ASICs might not have explicit initial values, they are crucial for simulation
and FPGA implementation. FPGA designs often require constant initial values, while
simulation allows for more complex initialization using expressions like function calls
`$random`, `$readmem`, and `$fopen`.

seq.compreg has a (optional) powerOn operand that is lowered into inlined assignment in SV which allows users to initialize registers with user-specified values. However this representation is not sufficient for initialization with function calls. 

In order to represent various kinds of initialization, `seq.initial` op and `!seq.immutable` type
are introduced. The `seq.initial` operation produces values with types wrapped in `!seq.immutable`.
The `!seq.immutable` type wrapper prevents initial values from depending on time-variant values.
Stateful operations typically require corresponding initial values with the `!seq.immutable` type.
This ensures that the initial state of the operation is well-defined and independent of time-variant factors.

Example Input:
```mlir
%r_init, %u_init = seq.initial {
  %rand = sv.macro.ref.se @random() : () -> i32
  %c0_i32 = hw.constant 0 : i32
  seq.yield %rand, %c0_i32 : i32, i32
} : !seq.immutable<i32>, !seq.immutable<i32>
%r = seq.compreg %i, %clk initial %r_init : i32
%u = seq.compreg %i, %clk initial %u_init : i32
```

Output Verilog:
```verilog
reg [31:0] r;
initial
  r = `RANDOM;
reg [31:0] u = 32'h0;
```
  • Loading branch information
uenoku authored Aug 30, 2024
1 parent 79af01e commit b937bcf
Show file tree
Hide file tree
Showing 27 changed files with 588 additions and 120 deletions.
37 changes: 35 additions & 2 deletions docs/Dialects/Seq/RationaleSeq.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ addressing, meaning that this operation sets / reads the entire value.
- **reset**: Signal to set the state to 'resetValue'. Optional.
- **resetValue**: A value which the state is set to upon reset. Required iff
'reset' is present.
- **powerOn**: A value which will be assigned to the register upon system power-on.
- **initialValue**: An initial value for registers. This represents a system power-on value in FPGA.
- **name**: A name for the register, defaults to `""`. Inferred from the textual
SSA value name, or passed explicitly in builder APIs. The name will be passed to
the `sv.reg` during lowering.
- **innerSym**: An optional symbol to refer to the register. The symbol will be
passed to the `sv.reg` during lowering if present.

```mlir
%q = seq.compreg %input, %clk [ reset %reset, %resetValue ] [powerOn %powerOn] : $type(input)
%q = seq.compreg %input, %clk [ reset %reset, %resetValue ] [initial %initial] : $type(input)
```

Upon initialization, the state is defined to be uninitialized.
Expand Down Expand Up @@ -329,3 +329,36 @@ The fifo operation is configurable with the following parameters:
Like `seq.hlmem` there are no guarantees that all possible fifo configuration
are able to be lowered. Available lowering passes will pattern match on the
requested fifo configuration and attempt to provide a legal lowering.

## State Initialization

While ASICs might not have explicit initial values, they are crucial for simulation
and FPGA implementation. FPGA designs often require constant initial values, while
simulation allows for more complex initialization using expressions like function calls
`$random`, `$readmem`, and `$fopen`.

In order to represent various kinds of initialization, `seq.initial` op and `!seq.immutable` type
are introduced. The `seq.initial` operation produces values with types wrapped in `!seq.immutable`.
The `!seq.immutable` type wrapper prevents initial values from depending on time-variant values.
Stateful operations typically require corresponding initial values with the `!seq.immutable` type.
This ensures that the initial state of the operation is well-defined and independent of time-variant factors.

Example Input:
```mlir
%r_init, %u_init = seq.initial {
%rand = sv.macro.ref.se @RANDOM() : () -> i32
%c0_i32 = hw.constant 0 : i32
seq.yield %rand, %c0_i32 : i32, i32
} : !seq.immutable<i32>, !seq.immutable<i32>
%r = seq.compreg %i, %clk initial %r_init : i32
%u = seq.compreg %i, %clk initial %u_init : i32
```

Output Verilog:
```verilog
reg [31:0] r;
initial
r = `RANDOM;
reg [31:0] u = 32'h0;
```
9 changes: 9 additions & 0 deletions include/circt-c/Dialect/Seq.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ MLIR_CAPI_EXPORTED bool seqTypeIsAClock(MlirType type);
/// Creates an seq clock type
MLIR_CAPI_EXPORTED MlirType seqClockTypeGet(MlirContext ctx);

/// If the type is an immutable type
MLIR_CAPI_EXPORTED bool seqTypeIsAImmutable(MlirType type);

/// Creates a seq immutable type
MLIR_CAPI_EXPORTED MlirType seqImmutableTypeGet(MlirType type);

/// Creates a seq immutable type
MLIR_CAPI_EXPORTED MlirType seqImmutableTypeGetInnerType(MlirType type);

#ifdef __cplusplus
}
#endif
Expand Down
11 changes: 11 additions & 0 deletions include/circt/Dialect/Seq/SeqOps.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ struct FirMemory {
FirMemory(hw::HWModuleGeneratedOp op);
};

// Helper functions to create constant initial values.
mlir::TypedValue<seq::ImmutableType>
createConstantInitialValue(OpBuilder builder, Location loc,
mlir::IntegerAttr attr);
mlir::TypedValue<seq::ImmutableType>
createConstantInitialValue(OpBuilder builder, Operation *constantLike);

// Helper function to unwrap an immutable type value and get yield value in
// initial op.
Value unwrapImmutableValue(mlir::TypedValue<seq::ImmutableType> immutableVal);

} // namespace seq
} // namespace circt

Expand Down
69 changes: 56 additions & 13 deletions include/circt/Dialect/Seq/SeqOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

include "circt/Dialect/HW/HWOpInterfaces.td"
include "circt/Dialect/HW/HWTypes.td"
include "circt/Dialect/Sim/SimTypes.td"
include "circt/Dialect/Seq/SeqAttributes.td"
include "circt/Dialect/Seq/SeqOpInterfaces.td"
include "mlir/Interfaces/InferTypeOpInterface.td"
Expand All @@ -39,16 +40,16 @@ def CompRegOp : SeqOp<"compreg",
OptionalAttr<StrAttr>:$name,
Optional<I1>:$reset,
Optional<AnyType>:$resetValue,
Optional<AnyType>:$powerOnValue,
Optional<ImmutableType>:$initialValue,
OptionalAttr<InnerSymAttr>:$inner_sym
);
let results = (outs AnyType:$data);
let assemblyFormat = [{
(`sym` $inner_sym^)? `` custom<ImplicitSSAName>($name) $input `,` $clk
(`reset` $reset^ `,` $resetValue)?
(`powerOn` $powerOnValue^)? attr-dict `:` type($data)
(`initial` $initialValue^)? attr-dict `:` type($data)
custom<OptionalTypeMatch>(ref(type($data)), ref($resetValue), type($resetValue))
custom<OptionalTypeMatch>(ref(type($data)), ref($powerOnValue), type($powerOnValue))
custom<OptionalImmutableTypeMatch>(ref(type($data)), ref($initialValue), type($initialValue))
}];
let hasVerifier = 1;

Expand All @@ -58,22 +59,24 @@ def CompRegOp : SeqOp<"compreg",
return build($_builder, $_state, input.getType(), input, clk,
/*name*/ StringAttr(),
/*reset*/ Value(), /*resetValue*/ Value(),
/*powerOnValue*/ Value(), hw::InnerSymAttr());
/*initialValue*/ Value(),
hw::InnerSymAttr());
}]>,
/// Create a register with an inner_sym matching the register's name.
OpBuilder<(ins "Value":$input, "Value":$clk, "StringAttrOrRef":$name), [{
auto nameAttr = name.get($_builder.getContext());
return build($_builder, $_state, input.getType(), input, clk, nameAttr,
/*reset*/ Value(), /*resetValue*/ Value(),
/*powerOnValue*/ Value(), hw::InnerSymAttr::get(nameAttr));
/*initialValue*/ Value(),
hw::InnerSymAttr::get(nameAttr));
}]>,
/// Create a register with a reset, with an inner_sym matching the
/// register's name, and optional power-on value.
/// register's name, and optional power-on value.
OpBuilder<(ins "Value":$input, "Value":$clk, "Value":$reset, "Value":$rstValue,
"StringAttrOrRef":$name, CArg<"Value", "{}">:$powerOnValue), [{
"StringAttrOrRef":$name, CArg<"Value", "{}">:$initialValue), [{
auto nameAttr = name.get($_builder.getContext());
return build($_builder, $_state, input.getType(), input, clk, nameAttr,
reset, rstValue, powerOnValue, hw::InnerSymAttr::get(nameAttr));
reset, rstValue, initialValue, hw::InnerSymAttr::get(nameAttr));
}]>
];
}
Expand All @@ -96,16 +99,16 @@ def CompRegClockEnabledOp : SeqOp<"compreg.ce",
OptionalAttr<StrAttr>:$name,
Optional<I1>:$reset,
Optional<AnyType>:$resetValue,
Optional<AnyType>:$powerOnValue,
Optional<ImmutableType>:$initialValue,
OptionalAttr<InnerSymAttr>:$inner_sym
);
let results = (outs AnyType:$data);
let assemblyFormat = [{
(`sym` $inner_sym^)? `` custom<ImplicitSSAName>($name) $input `,` $clk `,` $clockEnable
(`reset` $reset^ `,` $resetValue)?
(`powerOn` $powerOnValue^)? attr-dict `:` type($data)
(`initial` $initialValue^)? attr-dict `:` type($data)
custom<OptionalTypeMatch>(ref(type($data)), ref($resetValue), type($resetValue))
custom<OptionalTypeMatch>(ref(type($data)), ref($powerOnValue), type($powerOnValue))
custom<OptionalImmutableTypeMatch>(ref(type($data)), ref($initialValue), type($initialValue))
}];
let hasVerifier = 1;

Expand All @@ -120,11 +123,11 @@ def CompRegClockEnabledOp : SeqOp<"compreg.ce",
}]>,
OpBuilder<(ins "Value":$input, "Value":$clk, "Value":$ce,
"Value":$reset, "Value":$rstValue, "StringRef":$name,
CArg<"Value", "{}">:$powerOnValue),
CArg<"Value", "{}">:$initialValue),
[{
auto nameAttr = StringAttr::get($_builder.getContext(), name);
return build($_builder, $_state, input.getType(), input, clk, ce,
nameAttr, reset, rstValue, powerOnValue,
nameAttr, reset, rstValue, initialValue,
hw::InnerSymAttr::get(nameAttr));
}]>,
];
Expand Down Expand Up @@ -697,3 +700,43 @@ def FromClockOp : SeqOp<"from_clock", [Pure]> {
let hasFolder = 1;
let hasCanonicalizeMethod = 1;
}

def InitialOp : SeqOp<"initial", [SingleBlock,
SingleBlockImplicitTerminator<"YieldOp">,
RecursivelySpeculatable,
RecursiveMemoryEffects, IsolatedFromAbove]> {
let summary = "Operation that produces values for initialization";
let description = [{
`seq.initial` op creates values wrapped types with !seq.immutable.
See the Seq dialect rationale for a longer description.
}];

let arguments = (ins);
let results = (outs Variadic<ImmutableType>); // seq.immutable values
let regions = (region SizedRegion<1>:$body);
let hasVerifier = 1;
let skipDefaultBuilders = 1;
let builders = [
OpBuilder<(ins CArg<"TypeRange", "{}">:$resultTypes, CArg<"std::function<void()>", "{}">:$ctor)>
];

let assemblyFormat = [{
$body attr-dict `:` type(results)
}];

let extraClassDeclaration = [{
Block *getBodyBlock() { return &getBody().front(); }
}];
}

def YieldOp : SeqOp<"yield",
[Pure, Terminator, HasParent<"InitialOp">]> {
let summary = "Yield values";

let arguments = (ins Variadic<AnyType>:$operands);
let builders = [
OpBuilder<(ins), "build($_builder, $_state, std::nullopt);">
];

let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
}
21 changes: 21 additions & 0 deletions include/circt/Dialect/Seq/SeqTypes.td
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,25 @@ def ClockType : SeqType<"Clock"> {
let mnemonic = "clock";
}

def ImmutableTypeImpl : SeqType<"Immutable"> {
let mnemonic = "immutable";

let summary = "Value type that is immutable after initialization";
let parameters = (ins "::mlir::Type":$innerType);

let assemblyFormat = "`<` $innerType `>`";

let builders = [
TypeBuilderWithInferredContext<(ins "Type":$innerType), [{
auto *ctx = innerType.getContext();
return $_get(ctx, innerType);
}]>
];
}

// A handle to refer to circt::seq::ImmutableType in ODS.
def ImmutableType : DialectType<SeqDialect,
CPred<"isa<circt::seq::ImmutableType>($_self)">,
"an ImmutableType", "::circt::seq::ImmutableType">;

#endif // CIRCT_DIALECT_SEQ_SEQTYPES
7 changes: 5 additions & 2 deletions integration_test/Bindings/Python/dialects/seq.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@
def top(module):
# CHECK: %[[RESET_VAL:.+]] = hw.constant 0
reg_reset = hw.ConstantOp.create(i32, 0).result
# CHECK: %[[POWERON_VAL:.+]] = hw.constant 42
poweron_value = hw.ConstantOp.create(i32, 42).result
# CHECK: %[[INPUT_VAL:.+]] = hw.constant 45
reg_input = hw.ConstantOp.create(i32, 45).result
# CHECK: %[[DATA_VAL:.+]] = seq.compreg %[[INPUT_VAL]], %clk reset %rst, %[[RESET_VAL]] powerOn %[[POWERON_VAL]]
# CHECK-NEXT: %[[POWERON_VAL:.+]] = seq.initial {
# CHECK-NEXT: %[[C42:.+]] = hw.constant 42 : i32
# CHECK-NEXT: seq.yield %[[C42]] : i32
# CHECK-NEXT: } : !seq.immutable<i32>
# CHECK: %[[DATA_VAL:.+]] = seq.compreg %[[INPUT_VAL]], %clk reset %rst, %[[RESET_VAL]] initial %[[POWERON_VAL]]
reg = seq.CompRegOp(i32,
reg_input,
module.clk,
Expand Down
9 changes: 9 additions & 0 deletions lib/Bindings/Python/SeqModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,13 @@ void circt::python::populateDialectSeqSubmodule(py::module &m) {
return cls(seqClockTypeGet(ctx));
},
py::arg("cls"), py::arg("context") = py::none());

mlir_type_subclass(m, "ImmutableType", seqTypeIsAImmutable)
.def_classmethod("get",
[](py::object cls, MlirType innerType) {
return cls(seqImmutableTypeGet(innerType));
})
.def_property_readonly("inner_type", [](MlirType self) {
return seqImmutableTypeGetInnerType(self);
});
}
20 changes: 18 additions & 2 deletions lib/Bindings/Python/dialects/seq.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

from . import hw
from . import seq
from .._mlir_libs._circt._seq import *
from ..dialects._ods_common import _cext as _ods_cext
from ..ir import IntegerType, OpView, StringAttr
from ..ir import IntegerType, OpView, StringAttr, InsertionPoint
from ..support import BackedgeBuilder, NamedValueOpView
from ._seq_ops_gen import *
from ._seq_ops_gen import _Dialect
from .seq import CompRegOp
from .seq import CompRegOp, InitialOp


# Create a computational register whose input is the given value, and is clocked
Expand Down Expand Up @@ -92,6 +93,21 @@ def __init__(self,
operands += [None, None]

if power_on_value is not None:
if isinstance(power_on_value.type, seq.ImmutableType):
pass
else:
if power_on_value.owner is None:
assert False, "Initial value must not be port"
elif isinstance(power_on_value.owner.opview, hw.ConstantOp):
init = InitialOp([seq.ImmutableType.get(power_on_value.type)])
init.body.blocks.append()
with InsertionPoint(init.body.blocks[0]):
cloned_constant = power_on_value.owner.clone()
seq.YieldOp(cloned_constant)

power_on_value = init.results[0]
else:
assert False, "Non-constant initial value not supported"
operands.append(power_on_value)
operand_segment_sizes.append(1)
else:
Expand Down
12 changes: 12 additions & 0 deletions lib/CAPI/Dialect/Seq.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,15 @@ bool seqTypeIsAClock(MlirType type) {
MlirType seqClockTypeGet(MlirContext ctx) {
return wrap(ClockType::get(unwrap(ctx)));
}

bool seqTypeIsAImmutable(MlirType type) {
return llvm::isa<ImmutableType>(unwrap(type));
}

MlirType seqImmutableTypeGet(MlirType innerType) {
return wrap(ImmutableType::get(unwrap(innerType)));
}

MlirType seqImmutableTypeGetInnerType(MlirType type) {
return wrap(llvm::cast<ImmutableType>(unwrap(type)).getInnerType());
}
Loading

0 comments on commit b937bcf

Please sign in to comment.