Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Arc] Add InitialOp and lowering support for FirReg preset values. #7480

Merged
merged 20 commits into from
Aug 15, 2024

Conversation

fzi-hielscher
Copy link
Contributor

@fzi-hielscher fzi-hielscher commented Aug 8, 2024

An ongoing effort to carve out a path through the Arc dialect from the LLVM end towards memory and register initializers and miscellaneous stuff that may happen once at the start of simulation. Please don't look too closely at the code, yet. But I'd be happy to know if I'm on the right path or have taken a wrong turn already.

Outdated. See comments below.
The central addition so far is an initial region to the Arc ModelOp. which is able to modifiy the state storage that gets passed to the body region e.g.,:

arc.model @counter io !hw.modty<input clk : i1, output o : i8> {
  ^bb0(%arg0: !arc.storage<4>):
    [...]
  } initial {
  ^bb0(%arg0: !arc.storage<4>):
    %c42_i8 = arith.constant 42 : i8
    %0 = arc.storage.get %arg0[3] : !arc.storage<4> -> !arc.state<i8>
    arc.state_write %0 = %c42_i8 : <i8>
    arc.yield_storage %arg0 : !arc.storage<4>
  }

The initial values are currently derived from an attribute attached to the AllocStateOp. Similarly, I intend to add an attribute for file based initialization to the AllocMemoryOp. What I have to figure out yet is how to best get these Attributes to those Ops during lowering. I'm reluctant to add even more stuff to the StateOp, although that seems like the obvious path.

@fabianschuiki
Copy link
Contributor

Really cool, thanks for working on this 🥳!

I like the idea of putting the initial (and maybe also final) stuff into a separate region and block. I'm wondering though if it's worth the hassle of adding a separate region to arc.model to capture the initial stuff, and correspondingly the final stuff. Could we use something like an arc.initial and arc.final op that has such a region and block instead, which you'd just have floating around in the arc.model op like everything else? That might make it easy to spit out initialization and finalization code during LowerState. State update legalization could then appropriately ignore these ops during legalization, and we could add a transform that collects all arc.initial ops into a common <Module>_initial and all arc.final ops into a common <Module>_final function. Would something like that work?

A benefit could be that the AllocStateOp and AllocMemoryOp themselves wouldn't have to deal with the initial values at all. During lowering, we could populate the appropriate write ops to the allocated storage to setup the initial state. That could also nicely work for file-based initialization, where we could actually generate a bit of procedural code to write directly to the storage.

You're right though about StateOp: we'd still need some way to annotate what the initial value is. If this is going to be an SSA value on the StateOp, we could sink as much of it into an arc.initial block during LowerState. I remember @uenoku was looking into adding more complicated initializers for registers, e.g. the results of DPI calls. That would work nicely here.

@fzi-hielscher
Copy link
Contributor Author

Could we use something like an arc.initial and arc.final op that has such a region and block instead, which you'd just have floating around in the arc.model op like everything else? That might make it easy to spit out initialization and finalization code during LowerState. State update legalization could then appropriately ignore these ops during legalization, and we could add a transform that collects all arc.initial ops into a common _initial and all arc.final ops into a common _final function. Would something like that work?

That sounds a lot like what I did yesterday and how I've managed the lowering of FirMemInit annotations previously. 😄 So, yeah, it does work. What made me reconsider this is that after hoisting the arc.initial ops out of the ModelOp into their own function, we lose the relation between the model and its initializer. It just happens to be an isolated function that is called <Module>_initial at that point. Now, that may be easily fixed by slapping a FlatSymbolRefAttr onto the ModelOp. But why not slap the entire region onto it? 😉

I kind of like the controlflow-ish expressiveness of the RegionBranchOpInterface, passing the state/storage from the initial to the body and then potentially to the final region. But, yes, it might just make our life more difficult in the end.

That could also nicely work for file-based initialization, where we could actually generate a bit of procedural code to write directly to the storage.

For raw binary images where we would just copy them into the storage I'd agree. But the common .mem file format is a bit more complex. It can be sparse, does not necessarily contain the contents in-order, can contain line and block comments, etc.. I'm very reluctant to write that parsing logic as MLIR generator. 😅

Thanks a lot for your feedback. I'll see what I can come up with over the next couple of days.

@uenoku
Copy link
Member

uenoku commented Aug 9, 2024

Really cool! Thank you for working on this!

Using attributes as initial values would be too restrictive for most of practical use cases such as memory initialization with $readmemh or firreg randomized initialization. So I generally prefer adding initial values as SSA values.

I'm currently working on similar change for seq dialect side in order to explicitly initialize registers with potentially side-effecting ops (such as $random or DPI calls). The design is not frozen yet but here is what I want to introduce for seq.

%dpi, %random = seq.initial {
  %0 = sim.call @foo : i32
  %1 = = sv.call @rand: i32
  seq.yield %0, %1: i32, i32
} : !seq.immutable<i32>,  !seq.immutable<i32> // types to indicate values are timing-invariant.


%a = seq.compreg %clock, reset %reset, initial %dpi_call : (seq.clock, i1, !seq.immutable<i32>) -> i32
%b = seq.compreg %clock, reset %reset, initial %random : (seq.clock, i1, !seq.immutable<i32>) -> i32

that will be lowered into following SV by normal firtool flow:

reg [31:0] a, b;
initial begin
   a = foo();
   b = rand();
end

I think probably we can do similar thing for Arc as well.

@fzi-hielscher
Copy link
Contributor Author

fzi-hielscher commented Aug 9, 2024

Cool, thanks for sharing @uenoku!

After our discussions regarding the hw.triggered op I've also started to try and figure out how the Sim dialect fits between Comb/Seq on the one side and LLHD on the other side. I'd love to know where you guys would draw the lines here. In essence, I think "what happens in Sim should stay in simulation", so I'd expect most of it to be guarded by ifndef SYNTHESIS after SV lowering. Initializers live in this weird in-between space as they may or may not be synthesizable, depending on the circumstances. Having an immutable type wrapper to indicate procedurally generated initial values is an interesting approach I have not considered yet.

For procedural regions in Sim I've drafted an operation which (unsurprisingly) works a lot like an Arc with latency 1:

%counterInit = hw.constant 1 : i8
%counter = sim.procedural_seq %counter on %someTrigger : !sim.trigger {
^bb0(%arg0 : i8)
  %cst1 = hw.constant 1 : i8
  %lit = sim.fmt.lit "Tick."
  sim.proc.print %lit
  %next = comb.add %arg0, %cst1 : i8
  sim.yield %next : i8
} initially %counterInit : (i8) -> i8

For non-simulation flows %counter would be tied off to %counterInit. !sim.trigger is intended to provide a "virtual" clock tree, where all operations attached to the same tree "atomically" produce their results, but have a (partial) order defined on their side-effects.

It would be really nice to string all of this together. In the end I just want support for FIRRTL printf and MemoryInitAttr, so I can replace my verilator simulations with arcilator. 😅

@fzi-hielscher
Copy link
Contributor Author

The ModelOp's region is out, the InitialOp is in. Basic smoke tests for lowering the preset of FirRegOps are working.

After much agonizing, I've added the initial values as SSA operands to the StateOp. To some extent I'd still prefer using a initializer region (for the StateOp, not the ModelOp) like it is done for LLVM GlobalOps. But this would make correlating initializers of different registers like in @uenoku's example difficult.

I'm currently assuming that states without initializers are implicitly zero initialized. As far as I can see we are always providing a zeroed storage to the model. An arc.uninitialized op and attribute might still be worth adding at some point.

@@ -659,11 +680,12 @@ def ModelOp : ArcOp<"model", [RegionKindInterface, IsolatedFromAbove,
specifies the I/O of the module associated to this model.
}];
let arguments = (ins SymbolNameAttr:$sym_name,
TypeAttrOf<ModuleType>:$io);
TypeAttrOf<ModuleType>:$io,
OptionalAttr<FlatSymbolRefAttr>:$initialFn);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SymbolUserOpInterface is necessary if we want to add initializer as a function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Missed that one.

Copy link
Member

@uenoku uenoku left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not competent reviewer for Arc but the design of initial op looks reasonable to me. Thank you for working on this. I'm a bit worried about adding "initialize 0 by default" logic to several places so in the future it would be nice to isolate the default initialization to a single pass.

@fzi-hielscher
Copy link
Contributor Author

fzi-hielscher commented Aug 13, 2024

Thank you for working on this. I'm a bit worried about adding "initialize 0 by default" logic to several places so in the future it would be nice to isolate the default initialization to a single pass.

Yeah, we should probably add arc.unintialized asap and make that the implicit default. But this PR has already ballooned quite a bit, so I'd do it in a separate one.

@fzi-hielscher fzi-hielscher changed the title [WIP][Arc] Add support for initializers and initial blocks [Arc] Add InitialOp and lowering support for FirReg preset values. Aug 13, 2024
@fzi-hielscher
Copy link
Contributor Author

fzi-hielscher commented Aug 13, 2024

Right - I think this is a good point to draw a line and wait for @fabianschuiki and/or @maerhart to point out my blind spots. 😬

The basic lowering for a seq.firreg now looks like this:

%reg = seq.firreg %input clock %clock preset 127 : i8

After StripSV:

%c127_i8 = hw.constant 127 : i8
%reg = seq.compreg %input, %clock powerOn %c127_i8 : i8

After ConvertToArcs:

%c127_i8 = hw.constant 127 : i8
%0 = arc.state @Foo_arc(%input) clock %clock initial (%c127_i8 : i8) latency 1 : (i8) -> i8

After LowerState:

%1 = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state<i8>
[...]
arc.initial {
  %c127_i8 = hw.constant 127 : i8
  arc.state_write %1 = %c127_i8 : <i8>
}

After LowerClocksToFuncs:

func.func @Foo_initial(%arg0: !arc.storage<6>) {
  %c127_i8 = hw.constant 127 : i8
  %0 = arc.storage.get %arg0[4] : !arc.storage<6> -> !arc.state<i8>
  arc.state_write %0 = %c127_i8 : <i8>
  call @Foo_passthrough(%arg0) : (!arc.storage<6>) -> ()
  return
}

arc.model @Foo io !hw.modty<input clock : !seq.clock, input input : i8, output o : i8> initializer @Foo_initial { [...] }

@fzi-hielscher fzi-hielscher marked this pull request as ready for review August 13, 2024 18:18
Copy link
Member

@maerhart maerhart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for working on that! Such a cool new arcilator feature 🎉

Comment on lines 469 to 471
let extraClassDeclaration = [{
mlir::Block &getBodyBlock() { return getBody().front(); }
}];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SingleBlock trait auto-generates this if you also rename the body region to bodyRegion (or similar).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks. This was just copy pasted from the PassThroughOp, so I guess we could add SingleBlock there, too.

mlir::Block &getBodyBlock() { return getBody().front(); }
}];
let builders = [
OpBuilder<(ins), "build($_builder, $_state, ValueRange{});">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this builder is not generated by default? What would the ValueRange do if we'd pass one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right That was a left-over from a point where I had InitialOp IsolatedFromAbove and gave it an argument list.

@@ -452,6 +460,20 @@ def PassThroughOp : ArcOp<"passthrough", [NoTerminator, NoRegionArguments]> {
}];
}

def InitialOp : ArcOp<"initial", [RecursiveMemoryEffects, NoTerminator, NoRegionArguments]> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add HasParent<"ModelOp"> or are there reasons why we want the additional flexibility?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to restrict it. Again, probably same for PassThroughOp?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I think it also makes sense to add it for PassThroughOp.

@@ -452,6 +460,20 @@ def PassThroughOp : ArcOp<"passthrough", [NoTerminator, NoRegionArguments]> {
}];
}

def InitialOp : ArcOp<"initial", [RecursiveMemoryEffects, NoTerminator, NoRegionArguments]> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks looks like more than 80 cols to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can proudly announce that I have now enabled the vertical ruler for TableGen files in my editor.

@@ -652,18 +674,20 @@ def TapOp : ArcOp<"tap"> {
}

def ModelOp : ArcOp<"model", [RegionKindInterface, IsolatedFromAbove,
NoTerminator, Symbol]> {
NoTerminator, Symbol,
DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More than 80 cols?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. 😞

Comment on lines 135 to 136
assert(!modelOp.getInitialFn() &&
"Model should not have an initializer at this point.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this emit a proper error message because I think the user could write up an input file that triggers this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've turned it into a warning. We could be very thorough and add a call from the generated initializer to the preexisting one. But I don't really see a use case for that.

} else if (isa<InitialOp>(clockOp)) {
assert(!modelOp.getInitialFn() &&
"Model should not have an initializer at this point.");
modelOp.setInitialFnAttr(FlatSymbolRefAttr::get(funcOp.getSymNameAttr()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if there are multiple initial blocks? Do we merge them before this pass?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I assume only one exists. But I don't have a strong opinion on this and merging them should be trivial. Maybe we should add a region verifier to the ModelOp, that checks the number of InitialOps (and PassThroughOps)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only allowing one sounds like a good idea to me. I think in general there is nothing wrong with having multiple ones, so maybe just check it in this pass and emit an error that it is not supported (yet) by the pass?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this is a nice starting point! We can always relax this and collect multiple arc.initial ops into the init function later on.

Comment on lines +146 to +150
funcName.clear();
funcName.append(modelOp.getName());
funcName.append("_passthrough");
builder.create<func::CallOp>(clockOp->getLoc(), funcName, TypeRange{},
ValueRange{funcOp.getBody().getArgument(0)});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A passthrough function is only generated if a arc.passthrough op was present. Should we keep track of whether that was the case and only insert this call in that case? Or should we generate a dummy passthrough function in this pass even if no passthrough op was present? Personally, I'd opt for the earlier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. It should now work without a PassThroughOp.

builder.create<func::CallOp>(clockOp->getLoc(), funcOp,
ValueRange{modelStorageArg});
} else if (isa<InitialOp>(clockOp)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: maybe use a TypeSwitch?

)
if model.hasInitialFn:
print(f" {model.name}_initial(&storage[0]);")
print(" }")
print(f" void eval() {{ {model.name}_eval(&storage[0]); }}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for also updating this python script!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool!

@fzi-hielscher
Copy link
Contributor Author

Many thanks for the detailed review, @maerhart .

@fzi-hielscher
Copy link
Contributor Author

LowerClocksToFuncs now checks that there is no more than one InitialOp and PassThroughOp in the model.

I've also taken the liberty to move the ODS traits for ClockTreeOp, InitialOp, and PassThroughOp to a common ClockTreeLikeOp class. Compared to before this adds RecursiveMemoryEffects, SingleBlock, and HasParent<"ModelOp"> to both ClockTreeOp and PassThroughOp. This seem correct to me. I hope it doesn't cause any unintended side effects.

Copy link
Contributor

@fabianschuiki fabianschuiki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fantastic! Thanks for all the diligent checks, tests, and error messages that this adds. I also like your choice of using an SSA value as the initial value, since it isn't entirely clear yet how isolated the initializers are going to be in practice.

Can you check if the Rocket core in arc-tests still runs with your changes? I don't see why not, but just as a sanity check. Rocket should be updated to work with newer firtool versions. Boom probably doesn't work with current firtool anymore.

Comment on lines -151 to +157
(`reset` $reset^)? `latency` $latency attr-dict
`:` functional-type($inputs, results)
(`reset` $reset^)?
( `initial` ` ` `(` $initials^ `:` type($initials) `)`)?
`latency` $latency attr-dict `:` functional-type($inputs, results)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could a OptionalTypesMatchWith constraint between the $initials types and $results types make the : type($initials) part redundant? Would be cool if we could get the generated parser to infer the types based on the results 😃

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OptionalTypesMatchWith sadly doesn't appear to work with type ranges. RangedTypesMatchWith on the other hand refuses to infer type($initials) even if I make it non-optional. Maybe it cannot infer an operand range from a result range?!? ODS continues to surprise me for better or worse. I can still imagine that it could be done somehow, but at that point I had thrown in the towel.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah there might be something about results having to be derived from operands to make ODS do the magic automatically. Thanks for trying! 🥳

}
return
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is really fantastic!

Comment on lines +13 to +18
arc.initial {
%cst0 = llvm.mlir.constant(0 : i32) : i32
%stderr = llvm.call @_arc_env_get_print_stream(%cst0) : (i32) -> !llvm.ptr
%str = llvm.mlir.addressof @global_init_str : !llvm.ptr
%0 = llvm.call @_arc_libc_fputs(%str, %stderr) : (!llvm.ptr, !llvm.ptr) -> i32
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! I guess a future arc.final op could work pretty much in the exact same way, with just a different point in time when it's called. Really neat!

Comment on lines +315 to +331
auto referencedOp =
symbolTable.lookupNearestSymbolFrom(*this, getInitialFnAttr());
if (!referencedOp)
return emitError("Cannot find declaration of initializer function '")
<< *getInitialFn() << "'.";
auto funcOp = dyn_cast<func::FuncOp>(referencedOp);
if (!funcOp) {
auto diag = emitError("Referenced initializer must be a 'func.func' op.");
diag.attachNote(referencedOp->getLoc()) << "Initializer declared here:";
return diag;
}
if (!llvm::equal(funcOp.getArgumentTypes(), getBody().getArgumentTypes())) {
auto diag = emitError("Arguments of initializer function must match "
"arguments of model body.");
diag.attachNote(referencedOp->getLoc()) << "Initializer declared here:";
return diag;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice idea checking that the function signature is reasonable.

This makes me think that we might want to create something like a arc.lowered_model op in the future which has no body region, but has symbol refs to the init, eval, and final functions, and a symbol ref to the data layout (@maerhart has been working on a prototype to move the sim memory layout into a struct-like op). That would allow us to keep all information about the generated models around until the very end, without restricting the conversion to functions and further optimizations. Anyway, very much future stuff 😉

Comment on lines +179 to +180
// TODO: There are some other ops we probably want to allow
return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice defensive start 🥳

Comment on lines +155 to +161
// Materialize initial value, assume zero initialization as default.
if (reg.getPreset() && !reg.getPreset()->isZero()) {
assert(hw::type_isa<IntegerType>(reg.getType()) &&
"cannot lower non integer preset");
presetValue = builder.createOrFold<hw::ConstantOp>(
reg.getLoc(), IntegerAttr::get(reg.getType(), *reg.getPreset()));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of having an implicit initialization value as a default, e.g. 0 for two-valued, X for four-valued, and U for nine-valued integers, and then only add explicit initializers if they differ. Most SV designs will not have initializers on their registers, so that will make the common case less verbose 👍

@fzi-hielscher
Copy link
Contributor Author

Can you check if the Rocket core in arc-tests still runs with your changes? I don't see why not, but just as a sanity check. Rocket should be updated to work with newer firtool versions. Boom probably doesn't work with current firtool anymore.

It's working, and I much appreciated you updating the FIRRTL models. Now, if we could find a way to allow the printfs back in, I'd be even happier. 😄
Annoyingly, adding a preset value currently breaks the lockstep simulation with verilator, even after adding the -DENABLE_INITIAL_REG_ flag. I haven't had the time to properly look into that, yet. I don't know how verilator handles initialization and by calling the passthrough function if, but only if, there is an initial function, the state before the first eval call is somewhat ill-defined right now.

As much as I hate saying it, but we probably need some notion of the "pre-initial" state. This would at the bare minimum encompass the initially observable state of the input ports. And on top of that, there are of course the verilog peculiarities, like logic default initialization and the difference between initialization at the declaration vs. in an initial block. I.e., is the print here triggered or not:

logic a = 1'b0;
always @(a) $display("Yikes");

*sigh* I keep meaning to do a write-up of the (non-authoritative) conclusions I've drawn when messing up my mind with the entire four/nine valued logic stuff. I just cannot find the right place or time, and this PR certainly isn't it.

Again, thanks a lot for your feedback @fabianschuiki, @maerhart, @uenoku, and see you at the next PR. 😉

@fzi-hielscher fzi-hielscher merged commit 4201039 into llvm:main Aug 15, 2024
4 checks passed
@sequencer
Copy link
Contributor

Do we have a plan to provide chisel api to the preset?

@fabianschuiki
Copy link
Contributor

@fzi-hielscher Very cool stuff! I definitely expect us to have to iterate on initialization order and all that quite a bit. The SV standard is very peculiar and nebulous about when some of the initialization happens. And we may want to make our own pick regarding what the exact initialization is. For example, I think it would make sense if initialization would also include initial runs of dependent always blocks and the like, but that might be totally different from other simulators.

@fabianschuiki
Copy link
Contributor

@sequencer Do we have a plan to provide chisel api to the preset?

@uenoku has been poking at that.

@uenoku
Copy link
Member

uenoku commented Aug 15, 2024

@sequencer Do we have a plan to provide chisel api to the preset?
@uenoku has been poking at that.

Yes, we haven't sorted out IR design for FIRRTL but this is definitely on the plate. Constant preset value could be easily added but initialization with side-effecting op like DPI requires more design work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants