diff --git a/xls/codegen/BUILD b/xls/codegen/BUILD index 9a0884e6d3..7f2008b1ac 100644 --- a/xls/codegen/BUILD +++ b/xls/codegen/BUILD @@ -2066,6 +2066,7 @@ cc_test( "//xls/ir:function_builder", "//xls/ir:op", "//xls/ir:proc_elaboration", + "//xls/ir:value", "//xls/scheduling:pipeline_schedule", "//xls/scheduling:run_pipeline_schedule", "//xls/scheduling:scheduling_options", diff --git a/xls/codegen/clone_nodes_into_block_handler.cc b/xls/codegen/clone_nodes_into_block_handler.cc index 72d8d23ec8..7a3cd159c4 100644 --- a/xls/codegen/clone_nodes_into_block_handler.cc +++ b/xls/codegen/clone_nodes_into_block_handler.cc @@ -761,14 +761,20 @@ absl::Status CloneNodesIntoBlockHandler::HandleNextValue(Node* node, Proc* proc = function_base_->AsProcOrDie(); Next* next = node->As(); StateElement* state_element = - next->state_read()->As()->state_element(); + next->has_state_read() + ? next->state_read()->As()->state_element() + : next->state_element(); XLS_ASSIGN_OR_RETURN(int64_t index, proc->GetStateElementIndex(state_element)); StateRegister& state_register = *result_.state_registers.at(index); + bool equivalent_value = + next->has_state_read() + ? next->value() == next->state_read() + : next->value() == proc->GetStateReadByStateElement(state_element); state_register.next_values.push_back( {.stage = stage, - .value = next->value() == next->state_read() + .value = equivalent_value ? std::nullopt : std::make_optional(node_map_.at(next->value())), .predicate = diff --git a/xls/codegen/synchronous_procs_test.cc b/xls/codegen/synchronous_procs_test.cc index c73eda4a37..e5149ceace 100644 --- a/xls/codegen/synchronous_procs_test.cc +++ b/xls/codegen/synchronous_procs_test.cc @@ -38,6 +38,7 @@ #include "xls/ir/op.h" #include "xls/ir/package.h" #include "xls/ir/proc_elaboration.h" +#include "xls/ir/value.h" #include "xls/scheduling/pipeline_schedule.h" #include "xls/scheduling/run_pipeline_schedule.h" #include "xls/scheduling/scheduling_options.h" @@ -259,6 +260,64 @@ TEST_P(SynchronousProcsTest, ChainedProc) { absl_testing::IsOkAndHolds(outputs)); } +TEST_P(SynchronousProcsTest, DecoupledNextProc) { + Package package(TestBaseName()); + + TokenlessProcBuilder pb(NewStyleProc(), "my_proc", "tkn", &package); + Type* u32 = package.GetBitsType(32); + XLS_ASSERT_OK_AND_ASSIGN(ReceiveChannelInterface * in, + pb.AddInputChannel("top_in", u32)); + XLS_ASSERT_OK_AND_ASSIGN(SendChannelInterface * out, + pb.AddOutputChannel("top_out", u32)); + + XLS_ASSERT_OK_AND_ASSIGN(StateElement * st_elem, + pb.UnreadStateElement("accum", Value(UBits(0, 32)), + /*non_synthesizable=*/false)); + BValue st = pb.StateRead(st_elem); + BValue received = pb.Receive(in); + BValue next_st = pb.Add(st, received); + + pb.Next(st_elem, next_st); + pb.Send(out, next_st); + + XLS_ASSERT_OK_AND_ASSIGN(Proc * top, pb.Build({})); + XLS_ASSERT_OK(package.SetTop(top)); + + ASSERT_TRUE(top->uses_decoupled_next()); + + XLS_ASSERT_OK_AND_ASSIGN(ProcElaboration elab, + ProcElaboration::Elaborate(top)); + + XLS_ASSERT_OK_AND_ASSIGN(PackageSchedule package_schedule, + RunSynchronousPipelineSchedule( + &package, TestDelayEstimator(), + SchedulingOptions().clock_period_ps(1), elab)); + + ResetProto reset_proto; + reset_proto.set_name("rst"); + reset_proto.set_asynchronous(false); + reset_proto.set_active_low(false); + + XLS_ASSERT_OK_AND_ASSIGN( + CodegenResult result, + ToPipelineModuleText( + package_schedule, &package, + BuildPipelineOptions() + .reset(reset_proto.name(), reset_proto.asynchronous(), + reset_proto.active_low(), reset_proto.reset_data_path()) + .use_system_verilog(UseSystemVerilog()) + .emit_as_pipeline(false))); + + ModuleSimulator simulator = + NewModuleSimulator(result.verilog_text, result.signature, {}); + absl::flat_hash_map> inputs = { + {"top_in", {UBits(3, 32), UBits(10, 32), UBits(42, 32)}}}; + absl::flat_hash_map> outputs = { + {"top_out", {UBits(3, 32), UBits(13, 32), UBits(55, 32)}}}; + EXPECT_THAT(simulator.RunInputSeriesProc(inputs, {{"top_out", 3}}), + absl_testing::IsOkAndHolds(outputs)); +} + INSTANTIATE_TEST_SUITE_P(PipelineGeneratorTestInstantiation, SynchronousProcsTest, testing::ValuesIn(kDefaultSimulationTargets), diff --git a/xls/dev_tools/dev_passes/decompose_dataflow_pass.cc b/xls/dev_tools/dev_passes/decompose_dataflow_pass.cc index 15462fe74e..36c81f2f60 100644 --- a/xls/dev_tools/dev_passes/decompose_dataflow_pass.cc +++ b/xls/dev_tools/dev_passes/decompose_dataflow_pass.cc @@ -417,7 +417,13 @@ class DecomposeDataflowVisitor final : public DataflowVisitor { std::vector new_args; new_args.reserve(node->operands().size()); for (Node* arg : node->operands()) { - if (node->Is() && arg == node->As()->state_read()) { + if (node->Is() && + arg == (node->As()->has_state_read() + ? node->As()->state_read() + : node->function_base() + ->AsProcOrDie() + ->GetStateReadByStateElement( + node->As()->state_element()))) { // Don't decompose state reads. Leave pass-throughs alone and never // touch the original read. new_args.push_back(arg); diff --git a/xls/dev_tools/dev_passes/decompose_dataflow_pass_test.cc b/xls/dev_tools/dev_passes/decompose_dataflow_pass_test.cc index 41cc48bf09..39ca58f05a 100644 --- a/xls/dev_tools/dev_passes/decompose_dataflow_pass_test.cc +++ b/xls/dev_tools/dev_passes/decompose_dataflow_pass_test.cc @@ -118,6 +118,21 @@ TEST_F(DecomposeDataflowPassTest, ProcTest) { m::ArrayIndex(m::Literal(), {m::Literal(3)}))); } +TEST_F(DecomposeDataflowPassTest, DecoupledProcPassThroughTest) { + auto p = CreatePackage(); + TokenlessProcBuilder pb(NewStyleProc(), "p", "tkn", p.get()); + XLS_ASSERT_OK_AND_ASSIGN(StateElement * state_element, + pb.UnreadStateElement("st", Value(UBits(42, 32)), + /*non_synthesizable=*/false)); + BValue st = pb.StateRead(state_element); + pb.Next(state_element, st); + + XLS_ASSERT_OK_AND_ASSIGN(Proc * proc, pb.Build()); + ASSERT_TRUE(proc->uses_decoupled_next()); + + EXPECT_THAT(Run(p.get()), IsOkAndHolds(false)); +} + TEST_F(DecomposeDataflowPassTest, OneHotSelect) { auto p = CreatePackage(); FunctionBuilder fb("f", p.get()); diff --git a/xls/dev_tools/extract_state_element.cc b/xls/dev_tools/extract_state_element.cc index aefe342477..8ffdbfaa6d 100644 --- a/xls/dev_tools/extract_state_element.cc +++ b/xls/dev_tools/extract_state_element.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "absl/algorithm/container.h" @@ -123,13 +124,16 @@ absl::Status ExtractSegmentInto(ProcBuilder& pb, Proc* original, XLS_ASSIGN_OR_RETURN(chan, new_pkg->GetChannel(r->channel_name())); } else { XLS_ASSIGN_OR_RETURN(auto orig_chan_ref, r->GetChannelRef()); - Channel* orig_chan = std::get(orig_chan_ref); - XLS_ASSIGN_OR_RETURN( - Type * map_ty, new_pkg->MapTypeFromOtherPackage(orig_chan->type())); + XLS_ASSIGN_OR_RETURN(Type * map_ty, new_pkg->MapTypeFromOtherPackage( + ChannelRefType(orig_chan_ref))); + absl::Span initial_values = {}; + if (std::holds_alternative(orig_chan_ref)) { + initial_values = std::get(orig_chan_ref)->initial_values(); + } XLS_ASSIGN_OR_RETURN( - chan, new_pkg->CreateStreamingChannel( - r->channel_name(), ChannelOps::kReceiveOnly, map_ty, - orig_chan->initial_values())); + chan, new_pkg->CreateStreamingChannel(r->channel_name(), + ChannelOps::kReceiveOnly, + map_ty, initial_values)); if (r->is_blocking()) { if (r->predicate()) { old_to_new[n] = @@ -188,22 +192,27 @@ absl::Status ExtractSegmentInto(ProcBuilder& pb, Proc* original, } } else if (n->Is()) { Next* nxt = n->As(); - if (absl::c_contains( - state_elements, - nxt->state_read()->As()->state_element())) { - if (nxt->predicate()) { + if (absl::c_contains(state_elements, nxt->state_element())) { + std::optional pred = + nxt->predicate().has_value() + ? std::make_optional( + BValue(old_to_new.at(*nxt->predicate()), &pb)) + : std::nullopt; + std::string name = nxt->HasAssignedName() ? nxt->GetName() : ""; + if (nxt->has_state_read()) { + old_to_new[n] = pb.Next(BValue(old_to_new.at(nxt->state_read()), &pb), + BValue(old_to_new.at(nxt->value()), &pb), + pred, nxt->label(), nxt->loc(), name) + .node(); + } else { + StateRead* state_read = + original->GetStateReadByStateElement(nxt->state_element()); + StateElement* new_se = + old_to_new.at(state_read)->As()->state_element(); old_to_new[n] = - pb.Next(BValue(old_to_new[nxt->state_read()], &pb), - BValue(old_to_new[nxt->value()], &pb), - BValue(old_to_new[*nxt->predicate()], &pb), nxt->label(), - nxt->loc(), nxt->HasAssignedName() ? nxt->GetName() : "") + pb.Next(new_se, BValue(old_to_new.at(nxt->value()), &pb), pred, + nxt->label(), nxt->loc(), name) .node(); - } else { - old_to_new[n] = pb.Next(BValue(old_to_new[nxt->state_read()], &pb), - BValue(old_to_new[nxt->value()], &pb), - std::nullopt, nxt->label(), nxt->loc(), - nxt->HasAssignedName() ? nxt->GetName() : "") - .node(); } } // Non-extracted nexts can be dropped. diff --git a/xls/dev_tools/extract_state_element_test.cc b/xls/dev_tools/extract_state_element_test.cc index 4270620aa3..ef0312cef7 100644 --- a/xls/dev_tools/extract_state_element_test.cc +++ b/xls/dev_tools/extract_state_element_test.cc @@ -96,5 +96,40 @@ TEST_F(ExtractStateElementTest, SendState) { ExpectEqualToGoldenFile(TestFilePath(TestName()), new_pkg->DumpIr()); } +TEST_F(ExtractStateElementTest, SendStateDecoupledNext) { + auto p = CreatePackage(); + TokenlessProcBuilder pb(NewStyleProc{}, TestName(), "tkn", p.get()); + XLS_ASSERT_OK_AND_ASSIGN(ReceiveChannelInterface * chan, + pb.AddInputChannel("inp_chan", p->GetBitsType(32))); + XLS_ASSERT_OK_AND_ASSIGN(StateElement * state_element_a, + pb.UnreadStateElement("a", Value(UBits(1, 32)), + /*non_synthesizable=*/false)); + XLS_ASSERT_OK_AND_ASSIGN(StateElement * state_element_b, + pb.UnreadStateElement("b", Value(UBits(1, 32)), + /*non_synthesizable=*/false)); + XLS_ASSERT_OK_AND_ASSIGN(StateElement * state_element_c, + pb.UnreadStateElement("c", Value(UBits(1, 32)), + /*non_synthesizable=*/false)); + XLS_ASSERT_OK_AND_ASSIGN(StateElement * state_element_d, + pb.UnreadStateElement("d", Value(UBits(1, 32)), + /*non_synthesizable=*/false)); + BValue a = pb.StateRead(state_element_a); + BValue b = pb.StateRead(state_element_b); + BValue c = pb.StateRead(state_element_c); + BValue d = pb.StateRead(state_element_d); + pb.Next(state_element_a, pb.Add(a, b)); + pb.Next(state_element_b, pb.Add(c, a)); + pb.Next(state_element_c, pb.Add(a, d)); + pb.Next(state_element_d, + pb.Add(pb.TupleIndex(pb.Receive(chan, pb.Literal(Value::Token())), 1), + c)); + XLS_ASSERT_OK_AND_ASSIGN(Proc * proc, pb.Build()); + XLS_ASSERT_OK_AND_ASSIGN( + auto new_pkg, + ExtractStateElementsInNewPackage(proc, {state_element_a, state_element_b}, + /*send_state_values=*/true)); + RecordProperty("ir", new_pkg->DumpIr()); + ExpectEqualToGoldenFile(TestFilePath(TestName()), new_pkg->DumpIr()); +} } // namespace } // namespace xls diff --git a/xls/dev_tools/testdata/extract_state_element_test_SendStateDecoupledNext.ir b/xls/dev_tools/testdata/extract_state_element_test_SendStateDecoupledNext.ir new file mode 100644 index 0000000000..26f3e82451 --- /dev/null +++ b/xls/dev_tools/testdata/extract_state_element_test_SendStateDecoupledNext.ir @@ -0,0 +1,19 @@ +package SendStateDecoupledNext + +chan a_value_chan(bits[32], id=1, kind=streaming, ops=send_only, flow_control=ready_valid, strictness=proven_mutually_exclusive) +chan b_value_chan(bits[32], id=2, kind=streaming, ops=send_only, flow_control=ready_valid, strictness=proven_mutually_exclusive) +chan c_chan(bits[32], id=3, kind=streaming, ops=receive_only, flow_control=ready_valid, strictness=proven_mutually_exclusive) + +top proc SendStateDecoupledNext(a: bits[32], b: bits[32], init={1, 1}) { + literal.1: token = literal(value=token, id=1) + receive.10: (token, bits[32]) = receive(literal.1, channel=c_chan, id=10) + a: bits[32] = state_read(state_element=a, id=3) + b: bits[32] = state_read(state_element=b, id=6) + c: bits[32] = tuple_index(receive.10, index=1, id=11) + add.16: bits[32] = add(a, b, id=16) + add.17: bits[32] = add(c, a, id=17) + a__1: token = send(literal.1, a, channel=a_value_chan, id=5) + b__1: token = send(literal.1, b, channel=b_value_chan, id=8) + next_value.21: () = next_value(state_element=a, value=add.16, id=21) + next_value.22: () = next_value(state_element=b, value=add.17, id=22) +} diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessArray.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessArray.ir index 44476bb0ef..ff7cefe6cd 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessArray.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessArray.ir @@ -28,7 +28,7 @@ top proc __test_module__Array_0_next<>(__state: bits[32][4], init={[0, 1, 2, 3]} tuple.3: () = tuple(id=3) tuple_index.20: token = tuple_index(counted_for.19, index=0, id=20) assert.24: token = assert(__token, or.23, message="State element written before read in same activation.", id=24) - next_value.25: () = next_value(param=__state, value=new_array, predicate=literal.2, id=25) + next_value.25: () = next_value(state_element=__state, value=new_array, predicate=literal.2, id=25) tuple.26: () = tuple(id=26) tuple.27: () = tuple(id=27) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessBool.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessBool.ir index aaf8b0d28c..b16ece00db 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessBool.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessBool.ir @@ -12,7 +12,7 @@ top proc __test_module__Bool_0_next<>(__state: bits[1], init={0}) { tuple.3: () = tuple(id=3) false_val: bits[1] = identity(__state, id=5) assert.9: token = assert(__token, or.8, message="State element written before read in same activation.", id=9) - next_value.10: () = next_value(param=__state, value=literal.6, predicate=literal.2, id=10) + next_value.10: () = next_value(state_element=__state, value=literal.6, predicate=literal.2, id=10) tuple.11: () = tuple(id=11) tuple.12: () = tuple(id=12) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessConditionalWrite.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessConditionalWrite.ir index c9999512af..fa3d8adddf 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessConditionalWrite.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessConditionalWrite.ir @@ -39,10 +39,10 @@ top proc __test_module__main_0_next<>(__state: (bits[32], bits[1]), init={(0, 0) tuple.3: () = tuple(id=3) literal.6: bits[32] = literal(value=0, id=6) literal.12: bits[32] = literal(value=0, id=12) - next_value.22: () = next_value(param=__state, value=tuple.17, predicate=and.18, id=22) + next_value.22: () = next_value(state_element=__state, value=tuple.17, predicate=and.18, id=22) literal.24: bits[32] = literal(value=0, id=24) assert.38: token = assert(assert.34, or.37, message="State element written after write in same activation.", id=38) or.39: bits[1] = or(and.18, and.31, id=39) - next_value.40: () = next_value(param=__state, value=tuple.29, predicate=and.31, id=40) + next_value.40: () = next_value(state_element=__state, value=tuple.29, predicate=and.31, id=40) sel.42: () = sel(eq.11, cases=[tuple.41, tuple.23], id=42) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessLabeledReadAndWrite.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessLabeledReadAndWrite.ir index 94da6f910c..56f27d957d 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessLabeledReadAndWrite.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessLabeledReadAndWrite.ir @@ -13,7 +13,7 @@ top proc __test_module__main_0_next<>(__state: bits[32], init={0}) { y: bits[32] = add(x, literal.6, id=7) tuple.3: () = tuple(id=3) assert.10: token = assert(__token, or.9, message="State element written before read in same activation.", id=10) - next_value.11: () = next_value(param=__state, value=y, predicate=literal.2, label="main_write", id=11) + next_value.11: () = next_value(state_element=__state, value=y, predicate=literal.2, label="main_write", id=11) tuple.12: () = tuple(id=12) tuple.13: () = tuple(id=13) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMatch.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMatch.ir index 5fca02c1bb..16bbbd3391 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMatch.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMatch.ir @@ -45,14 +45,14 @@ top proc __test_module__main_0_next<>(__state: bits[32], init={0}) { tuple.36: () = tuple(id=36) tuple.50: () = tuple(id=50) tuple.3: () = tuple(id=3) - next_value.17: () = next_value(param=__state, value=add.11, predicate=and.13, id=17) + next_value.17: () = next_value(state_element=__state, value=add.11, predicate=and.13, id=17) tuple.18: () = tuple(id=18) - next_value.34: () = next_value(param=__state, value=umul.23, predicate=and.25, id=34) + next_value.34: () = next_value(state_element=__state, value=umul.23, predicate=and.25, id=34) tuple.35: () = tuple(id=35) literal.37: bits[1] = literal(value=1, id=37) assert.46: token = assert(assert.42, or.45, message="State element written after write in same activation.", id=46) or.47: bits[1] = or(or.33, and.39, id=47) - next_value.48: () = next_value(param=__state, value=current, predicate=and.39, id=48) + next_value.48: () = next_value(state_element=__state, value=current, predicate=and.39, id=48) tuple.49: () = tuple(id=49) priority_sel.52: () = priority_sel(concat.51, cases=[tuple.19, tuple.36], default=tuple.50, id=52) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMatchMultipleWrites.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMatchMultipleWrites.ir index 69b7d139d3..d3749677b9 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMatchMultipleWrites.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMatchMultipleWrites.ir @@ -51,16 +51,16 @@ top proc __test_module__main_0_next<>(__state: bits[1], init={1}) { tuple.29: () = tuple(id=29) tuple.58: () = tuple(id=58) tuple.3: () = tuple(id=3) - next_value.14: () = next_value(param=__state, value=literal.8, predicate=and.10, id=14) + next_value.14: () = next_value(state_element=__state, value=literal.8, predicate=and.10, id=14) tuple.15: () = tuple(id=15) - next_value.27: () = next_value(param=__state, value=literal.16, predicate=and.18, id=27) + next_value.27: () = next_value(state_element=__state, value=literal.16, predicate=and.18, id=27) tuple.28: () = tuple(id=28) eq.31: bits[1] = eq(literal.30, val, id=31) - next_value.43: () = next_value(param=__state, value=literal.32, predicate=and.34, id=43) + next_value.43: () = next_value(state_element=__state, value=literal.32, predicate=and.34, id=43) tuple.44: () = tuple(id=44) assert.54: token = assert(assert.50, or.53, message="State element written after write in same activation.", id=54) or.55: bits[1] = or(or.42, and.47, id=55) - next_value.56: () = next_value(param=__state, value=literal.45, predicate=and.47, id=56) + next_value.56: () = next_value(state_element=__state, value=literal.45, predicate=and.47, id=56) tuple.57: () = tuple(id=57) priority_sel.60: () = priority_sel(concat.59, cases=[tuple.29], default=tuple.58, id=60) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleBranchingLabeledReads.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleBranchingLabeledReads.ir index 1b7f7b3827..ba8f63672b 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleBranchingLabeledReads.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleBranchingLabeledReads.ir @@ -42,15 +42,15 @@ top proc __test_module__main_0_next<>(__val: bits[32], __switch: bits[1], init={ or.46: bits[1] = or(not.45, literal.2, id=46) not.44: bits[1] = not(switch_val, id=44) tuple.3: () = tuple(id=3) - next_value.15: () = next_value(param=__val, value=add.10, predicate=and.11, label="EvenWrite", id=15) + next_value.15: () = next_value(state_element=__val, value=add.10, predicate=and.11, label="EvenWrite", id=15) tuple.16: () = tuple(id=16) or.24: bits[1] = or(and.6, and.19, id=24) or.39: bits[1] = or(and.11, and.31, id=39) - next_value.40: () = next_value(param=__val, value=add.29, predicate=and.31, label="OddWrite", id=40) + next_value.40: () = next_value(state_element=__val, value=add.29, predicate=and.31, label="OddWrite", id=40) tuple.41: () = tuple(id=41) sel.43: () = sel(switch_val, cases=[tuple.42, tuple.17], id=43) assert.47: token = assert(assert.38, or.46, message="State element written before read in same activation.", id=47) - next_value.48: () = next_value(param=__switch, value=not.44, predicate=literal.2, id=48) + next_value.48: () = next_value(state_element=__switch, value=not.44, predicate=literal.2, id=48) tuple.49: () = tuple(id=49) tuple.50: () = tuple(id=50) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleStates.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleStates.ir index ca827df6c7..e1f9e7e856 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleStates.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleStates.ir @@ -36,12 +36,12 @@ top proc __test_module__main_0_next<>(__state_0: (bits[32], bits[32]), __state_1 tuple.3: () = tuple(id=3) literal.13: bits[32] = literal(value=1, id=13) literal.17: bits[32] = literal(value=0, id=17) - next_value.27: () = next_value(param=__state_0, value=new_a, predicate=literal.2, id=27) + next_value.27: () = next_value(state_element=__state_0, value=new_a, predicate=literal.2, id=27) tuple.28: () = tuple(id=28) - next_value.32: () = next_value(param=__state_1, value=new_b, predicate=literal.2, id=32) + next_value.32: () = next_value(state_element=__state_1, value=new_b, predicate=literal.2, id=32) tuple.33: () = tuple(id=33) assert.38: token = assert(assert.31, or.37, message="State element written before read in same activation.", id=38) - next_value.39: () = next_value(param=__state_2, value=add.35, predicate=literal.2, id=39) + next_value.39: () = next_value(state_element=__state_2, value=add.35, predicate=literal.2, id=39) tuple.40: () = tuple(id=40) tuple.41: () = tuple(id=41) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleStatesMultipleWrites.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleStatesMultipleWrites.ir index c7734662f5..1bad303da9 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleStatesMultipleWrites.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleStatesMultipleWrites.ir @@ -22,11 +22,11 @@ top proc __test_module__main_0_next<>(__state_0: bits[32], __state_1: bits[32], or.21: bits[1] = or(not.19, not.20, id=21) add.15: bits[32] = add(a, literal.14, id=15) tuple.3: () = tuple(id=3) - next_value.12: () = next_value(param=__state_1, value=add.8, predicate=literal.2, id=12) + next_value.12: () = next_value(state_element=__state_1, value=add.8, predicate=literal.2, id=12) tuple.13: () = tuple(id=13) assert.22: token = assert(assert.18, or.21, message="State element written after write in same activation.", id=22) or.23: bits[1] = or(literal.2, literal.2, id=23) - next_value.24: () = next_value(param=__state_1, value=add.15, predicate=literal.2, id=24) + next_value.24: () = next_value(state_element=__state_1, value=add.15, predicate=literal.2, id=24) tuple.25: () = tuple(id=25) tuple.26: () = tuple(id=26) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleStatesWriteBeforeRead.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleStatesWriteBeforeRead.ir index 948e497744..6d0b2a19d6 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleStatesWriteBeforeRead.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleStatesWriteBeforeRead.ir @@ -10,7 +10,7 @@ top proc __test_module__main_0_next<>(__state_0: bits[32], __state_1: bits[32], a: bits[32] = identity(__state_0, id=5) tuple.3: () = tuple(id=3) assert.7: token = assert(__token, not.6, message="State element written before read in same activation.", id=7) - next_value.8: () = next_value(param=__state_1, value=a, predicate=literal.2, id=8) + next_value.8: () = next_value(state_element=__state_1, value=a, predicate=literal.2, id=8) tuple.9: () = tuple(id=9) tuple.10: () = tuple(id=10) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleWrites.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleWrites.ir index d84a108cb6..d09a8183ae 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleWrites.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessMultipleWrites.ir @@ -21,11 +21,11 @@ top proc __test_module__main_0_next<>(__state: bits[32], init={0}) { or.20: bits[1] = or(not.18, not.19, id=20) accum__1: bits[32] = add(accum, literal.13, id=14) tuple.3: () = tuple(id=3) - next_value.11: () = next_value(param=__state, value=accum, predicate=literal.2, id=11) + next_value.11: () = next_value(state_element=__state, value=accum, predicate=literal.2, id=11) tuple.12: () = tuple(id=12) assert.21: token = assert(assert.17, or.20, message="State element written after write in same activation.", id=21) or.22: bits[1] = or(literal.2, literal.2, id=22) - next_value.23: () = next_value(param=__state, value=accum__1, predicate=literal.2, id=23) + next_value.23: () = next_value(state_element=__state, value=accum__1, predicate=literal.2, id=23) tuple.24: () = tuple(id=24) tuple.25: () = tuple(id=25) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessReadWithLabeledRead.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessReadWithLabeledRead.ir index cd1fea41be..2aec8fa163 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessReadWithLabeledRead.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessReadWithLabeledRead.ir @@ -22,7 +22,7 @@ top proc __test_module__main_0_next<>(__state: bits[32], init={0}) { tuple.3: () = tuple(id=3) or.10: bits[1] = or(literal.2, literal.2, id=10) assert.19: token = assert(assert.9, or.18, message="State element written before read in same activation.", id=19) - next_value.20: () = next_value(param=__state, value=y, predicate=literal.2, label="main_write", id=20) + next_value.20: () = next_value(state_element=__state, value=y, predicate=literal.2, label="main_write", id=20) tuple.21: () = tuple(id=21) tuple.22: () = tuple(id=22) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessS32.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessS32.ir index 7365ca13a7..ffa6ff638d 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessS32.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessS32.ir @@ -13,7 +13,7 @@ top proc __test_module__Counter_0_next<>(__state: bits[32], init={4294967291}) { add.7: bits[32] = add(current, literal.6, id=7) tuple.3: () = tuple(id=3) assert.10: token = assert(__token, or.9, message="State element written before read in same activation.", id=10) - next_value.11: () = next_value(param=__state, value=add.7, predicate=literal.2, id=11) + next_value.11: () = next_value(state_element=__state, value=add.7, predicate=literal.2, id=11) tuple.12: () = tuple(id=12) tuple.13: () = tuple(id=13) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessString.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessString.ir index 7b15cbad5e..38ea3dbb2d 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessString.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessString.ir @@ -12,7 +12,7 @@ top proc __test_module__String_0_next<>(__state: bits[8][5], init={[104, 101, 10 tuple.3: () = tuple(id=3) current: bits[8][5] = identity(__state, id=5) assert.9: token = assert(__token, or.8, message="State element written before read in same activation.", id=9) - next_value.10: () = next_value(param=__state, value=literal.6, predicate=literal.2, id=10) + next_value.10: () = next_value(state_element=__state, value=literal.6, predicate=literal.2, id=10) tuple.11: () = tuple(id=11) tuple.12: () = tuple(id=12) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessStruct.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessStruct.ir index 269c4f14cf..14e32a998f 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessStruct.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessStruct.ir @@ -18,6 +18,6 @@ top proc __test_module__Struct_0_next<>(__state: (bits[32], bits[32]), init={(0, shift: (bits[32], bits[32]) = tuple(add.8, add.11, id=12) tuple.3: () = tuple(id=3) assert.15: token = assert(__token, or.14, message="State element written before read in same activation.", id=15) - next_value.16: () = next_value(param=__state, value=shift, predicate=literal.2, id=16) + next_value.16: () = next_value(state_element=__state, value=shift, predicate=literal.2, id=16) tuple.17: () = tuple(id=17) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessU32.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessU32.ir index 301564b35f..56eee15288 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessU32.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessU32.ir @@ -13,7 +13,7 @@ top proc __test_module__Counter_0_next<>(__state: bits[32], init={0}) { y: bits[32] = add(x, literal.6, id=7) tuple.3: () = tuple(id=3) assert.10: token = assert(__token, or.9, message="State element written before read in same activation.", id=10) - next_value.11: () = next_value(param=__state, value=y, predicate=literal.2, id=11) + next_value.11: () = next_value(state_element=__state, value=y, predicate=literal.2, id=11) tuple.12: () = tuple(id=12) tuple.13: () = tuple(id=13) } diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessWriteBeforeRead.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessWriteBeforeRead.ir index cd75f578a5..eacf9b2111 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessWriteBeforeRead.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ExplicitStateAccessWriteBeforeRead.ir @@ -9,7 +9,7 @@ top proc __test_module__main_0_next<>(__state: bits[32], init={0}) { literal.4: bits[32] = literal(value=1, id=4) tuple.3: () = tuple(id=3) assert.6: token = assert(__token, not.5, message="State element written before read in same activation.", id=6) - next_value.7: () = next_value(param=__state, value=literal.4, predicate=literal.2, id=7) + next_value.7: () = next_value(state_element=__state, value=literal.4, predicate=literal.2, id=7) tuple.8: () = tuple(id=8) tuple.9: () = tuple(id=9) } diff --git a/xls/interpreter/proc_evaluator_test_base.cc b/xls/interpreter/proc_evaluator_test_base.cc index cdb40f7e79..7d89b82b7a 100644 --- a/xls/interpreter/proc_evaluator_test_base.cc +++ b/xls/interpreter/proc_evaluator_test_base.cc @@ -780,6 +780,34 @@ TEST_P(ProcEvaluatorTestBase, ConditionalNextProc) { EXPECT_THAT(queue.Read(), Optional(Value(UBits(2, 32)))); } +TEST_P(ProcEvaluatorTestBase, DecoupledNextProc) { + Package package(TestName()); + ProcBuilder pb("dec_next", &package); + XLS_ASSERT_OK_AND_ASSIGN( + StateElement * se, + pb.UnreadStateElement("state_element", Value(UBits(42, 32)), + /*non_synthesizable=*/false)); + pb.StateRead(se); + pb.Next(se, pb.Literal(UBits(56, 32))); + XLS_ASSERT_OK_AND_ASSIGN(Proc * proc, pb.Build()); + XLS_ASSERT_OK(package.SetTop(proc)); + + std::unique_ptr queue_manager = + GetParam().CreateQueueManager(&package); + std::unique_ptr evaluator = + GetParam().CreateEvaluator(proc, queue_manager.get()); + + std::unique_ptr continuation = evaluator->NewContinuation( + queue_manager->elaboration().GetUniqueInstance(proc).value()); + + EXPECT_THAT(continuation->GetState(), ElementsAre(Value(UBits(42, 32)))); + EXPECT_THAT( + evaluator->Tick(*continuation), + IsOkAndHolds(TickResult{.execution_state = TickExecutionState::kCompleted, + .channel_instance = std::nullopt, + .progress_made = true})); + EXPECT_THAT(continuation->GetState(), ElementsAre(Value(UBits(56, 32)))); +} TEST_P(ProcEvaluatorTestBase, CollidingNextValuesProc) { // Create an output-only proc which increments its counter value only every // other iteration - but also tries to set the counter value to a different diff --git a/xls/interpreter/proc_interpreter.cc b/xls/interpreter/proc_interpreter.cc index 8359e17a92..4175d78fb4 100644 --- a/xls/interpreter/proc_interpreter.cc +++ b/xls/interpreter/proc_interpreter.cc @@ -212,7 +212,10 @@ class ProcIrInterpreter : public IrInterpreter { return SetValueResult(next, Value::Tuple({})); } } - (*active_next_values_)[next->state_read()->As()->state_element()] + (*active_next_values_)[next->has_state_read() ? next->state_read() + ->As() + ->state_element() + : next->state_element()] .push_back(next); return SetValueResult(next, Value::Tuple({})); } diff --git a/xls/ir/ir_parser.cc b/xls/ir/ir_parser.cc index fce222be0e..cfbfff05a5 100644 --- a/xls/ir/ir_parser.cc +++ b/xls/ir/ir_parser.cc @@ -858,7 +858,10 @@ absl::StatusOr Parser::ParseNode( GetEffectiveProcOrError(fb, "next operations only supported in procs", op_token.pos()) .status()); - BValue* state_read = arg_parser.AddKeywordArg("param"); + std::optional* state_read = + arg_parser.AddOptionalKeywordArg("param"); + std::optional* state_element = + arg_parser.AddOptionalKeywordArg("state_element"); BValue* value = arg_parser.AddKeywordArg("value"); std::optional* predicate = arg_parser.AddOptionalKeywordArg("predicate"); @@ -869,8 +872,32 @@ absl::StatusOr Parser::ParseNode( label_string = label->value().value; } XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/0)); - bvalue = fb->Next(*state_read, *value, *predicate, label_string, *loc, - node_name); + if (state_read->has_value() && state_element->has_value()) { + return absl::InvalidArgumentError( + "next_value cannot specify both 'param' (StateRead) and " + "'state_element'"); + } + if (!state_read->has_value() && !state_element->has_value()) { + return absl::InvalidArgumentError( + "next_value must specify either 'param' (StateRead) or " + "'state_element'"); + } + if (state_read->has_value()) { + bvalue = fb->Next(state_read->value(), *value, *predicate, label_string, + *loc, node_name); + } else { + Proc* proc = fb->function()->AsProcOrDie(); + if (!proc->HasStateElement(state_element->value().value)) { + return absl::InvalidArgumentError(absl::StrFormat( + "State element '%s' not found for next_value @ %s", + state_element->value().value, op_token.pos().ToHumanString())); + } + XLS_ASSIGN_OR_RETURN( + StateElement * state_element, + proc->GetStateElementByName(state_element->value().value)); + bvalue = fb->Next(state_element, *value, *predicate, label_string, *loc, + node_name); + } break; } case Op::kCountedFor: { diff --git a/xls/ir/ir_parser_test.cc b/xls/ir/ir_parser_test.cc index 8430622b86..0da72e07b4 100644 --- a/xls/ir/ir_parser_test.cc +++ b/xls/ir/ir_parser_test.cc @@ -619,6 +619,27 @@ proc foo( x: bits[32], y: (), z: bits[32], init={42, (), 123}) { EXPECT_THAT(reads.front()->predicate(), std::nullopt); } +TEST(IrParserTest, DecoupledNextProc) { + std::string program = R"( +package test + +proc decoupled_proc(__state: bits[32], init={0}) { + sr: bits[32] = state_read(state_element=__state) + one: bits[32] = literal(value=1) + add: bits[32] = add(sr, one) + next_state: () = next_value(state_element=__state, value=add) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(program)); + XLS_ASSERT_OK_AND_ASSIGN(Proc * proc, package->GetProc("decoupled_proc")); + EXPECT_EQ(proc->GetStateElementCount(), 1); + XLS_ASSERT_OK_AND_ASSIGN(StateElement * state, + proc->GetStateElementByName("__state")); + absl::Span reads = proc->GetStateReadsByStateElement(state); + ASSERT_EQ(reads.size(), 1); + EXPECT_THAT(reads.front()->predicate(), std::nullopt); +} + TEST(IrParserTest, ProcWithPredicatedStateRead) { std::string program = R"( package test diff --git a/xls/ir/node.cc b/xls/ir/node.cc index f652fe1a5e..af287b23e3 100644 --- a/xls/ir/node.cc +++ b/xls/ir/node.cc @@ -641,10 +641,12 @@ std::string Node::ToStringInternal(bool include_operand_types) const { } case Op::kNext: { const Next* next = As(); - std::string param_name = next->has_state_read() - ? next->state_read()->GetName() - : next->state_element()->name(); - args = {absl::StrFormat("param=%s", param_name), + std::string target_arg = + next->has_state_read() + ? absl::StrFormat("param=%s", next->state_read()->GetName()) + : absl::StrFormat("state_element=%s", + next->state_element()->name()); + args = {target_arg, absl::StrFormat("value=%s", next->value()->GetName())}; std::optional predicate = next->predicate(); if (predicate.has_value()) { diff --git a/xls/jit/function_base_jit.cc b/xls/jit/function_base_jit.cc index 212cb5f3f2..95326f38e3 100644 --- a/xls/jit/function_base_jit.cc +++ b/xls/jit/function_base_jit.cc @@ -593,7 +593,13 @@ absl::StatusOr BuildPartitionFunction( if (node->Is()) { // next_value nodes store their output in the state-read's location, and // return nothing themselves. - StateRead* state_read = node->As()->state_read()->As(); + StateRead* state_read = + node->As()->has_state_read() + ? node->As()->state_read()->As() + : node->function_base() + ->AsProcOrDie() + ->GetStateReadByStateElement( + node->As()->state_element()); XLS_RET_CHECK(allocator.GetAllocationKind(state_read) == AllocationKind::kNone); output_buffers = wrapper.GetOutputBuffers(state_read, b); diff --git a/xls/jit/ir_builder_visitor.cc b/xls/jit/ir_builder_visitor.cc index 41692e8739..1bd42a0511 100644 --- a/xls/jit/ir_builder_visitor.cc +++ b/xls/jit/ir_builder_visitor.cc @@ -670,9 +670,13 @@ absl::Status InvokeAssertCallback(llvm::IRBuilder<>* builder, absl::Status InvokeNextValueCallback(llvm::IRBuilder<>* builder, Next* next, llvm::Value* instance_ctx) { llvm::Type* void_type = llvm::Type::getVoidTy(builder->getContext()); + StateElement* state_element = + next->has_state_read() + ? next->state_read()->As()->state_element() + : next->state_element(); llvm::Value* state_element_idx = builder->getInt64( *next->function_base()->AsProcOrDie()->GetStateElementIndex( - next->state_read()->As()->state_element())); + state_element)); llvm::Value* next_value = builder->getInt64(next->id()); InvokeCallback( builder, void_type, instance_ctx, {state_element_idx, next_value}); @@ -2851,7 +2855,11 @@ absl::Status IrBuilderVisitor::HandleNeg(UnOp* neg) { } absl::Status IrBuilderVisitor::HandleNext(Next* next) { - std::vector param_names({"param", "value"}); + std::vector param_names; + if (next->has_state_read()) { + param_names.push_back("param"); + } + param_names.push_back("value"); if (next->predicate().has_value()) { param_names.push_back("predicate"); } @@ -2860,7 +2868,8 @@ absl::Status IrBuilderVisitor::HandleNext(Next* next) { NewNodeIrContext(next, param_names, /*include_wrapper_args=*/true)); llvm::IRBuilder<>& b = node_context.entry_builder(); - llvm::Value* value_ptr = node_context.GetOperandPtr(Next::kValueOperand); + llvm::Value* value_ptr = + node_context.GetOperandPtr(next->value_operand_number()); if (!next->predicate().has_value()) { LlvmMemcpy(node_context.GetOutputPtr(0), value_ptr, @@ -2875,7 +2884,8 @@ absl::Status IrBuilderVisitor::HandleNext(Next* next) { } // If the predicate is true, emulate the `next_value` node's effects. - llvm::Value* predicate = Truthiness(node_context.LoadOperand(2), b); + llvm::Value* predicate = Truthiness( + node_context.LoadOperand(next->predicate_operand_number().value()), b); LlvmIfThen if_then = CreateIfThen(predicate, b, next->GetName()); LlvmMemcpy(node_context.GetOutputPtr(0), value_ptr, diff --git a/xls/passes/proc_state_range_query_engine.cc b/xls/passes/proc_state_range_query_engine.cc index f65623a14f..37651fcee4 100644 --- a/xls/passes/proc_state_range_query_engine.cc +++ b/xls/passes/proc_state_range_query_engine.cc @@ -539,7 +539,7 @@ absl::StatusOr> NarrowUsingSegments( for (Next* n : proc->next_values(state_element)) { // Nexts which don't update anything (either due to just being passthrough // or having a known-false predicate) don't need to be taken into account. - if (n->value() == n->state_read() || + if (n->value() == proc->GetStateReadByStateElement(state_element) || (n->predicate() && piqe.IsAllZeros(*n->predicate()))) { continue; } @@ -606,7 +606,7 @@ FindContextualRanges(Proc* proc, const QueryEngine& qe, // TODO(allight): We might want to use data-flow to better track whether // things have changed. This should probably be good enough in practice // however. - if (n->state_read() != n->value()) { + if (proc->GetStateReadByStateElement(state_element) != n->value()) { nexts.push_back(n); } } diff --git a/xls/passes/proc_state_range_query_engine_test.cc b/xls/passes/proc_state_range_query_engine_test.cc index cb5a57479e..5b262b22fb 100644 --- a/xls/passes/proc_state_range_query_engine_test.cc +++ b/xls/passes/proc_state_range_query_engine_test.cc @@ -76,6 +76,37 @@ TEST_F(ProcStateRangeQueryEngineTest, Negatives) { "[[0, 14]]"); } +TEST_F(ProcStateRangeQueryEngineTest, NegativesDecoupledNext) { + auto p = CreatePackage(); + ProcBuilder fb(TestName(), p.get()); + + XLS_ASSERT_OK_AND_ASSIGN(StateElement * state_element, + fb.UnreadStateElement("foo", Value(SBits(0, 32)), + /*non_synthesizable=*/false)); + + BValue st = fb.StateRead(state_element); + BValue res = fb.Add(st, fb.Literal(UBits(3, 32))); + BValue res_pos = fb.Add(st, fb.Literal(UBits(7, 32))); + + // Count -7 to 7 + auto in_loop = fb.SLt(st, fb.Literal(UBits(7, 32))); + fb.Next(state_element, fb.Add(st, fb.Literal(UBits(1, 32))), in_loop); + // If we aren't looping the value goes to -7 + fb.Next(state_element, fb.Literal(SBits(-7, 32)), fb.Not(in_loop)); + XLS_ASSERT_OK_AND_ASSIGN(Proc * proc, fb.Build()); + + ProcStateRangeQueryEngine qe; + XLS_ASSERT_OK(qe.Populate(proc).status()); + EXPECT_EQ( + IntervalSetTreeToString(qe.GetIntervals(st.node())), + absl::StrFormat("[[0, 7], [%v, %v]]", SBits(-7, 32), SBits(-1, 32))); + EXPECT_EQ( + IntervalSetTreeToString(qe.GetIntervals(res.node())), + absl::StrFormat("[[0, 10], [%v, %v]]", SBits(-7 + 3, 32), SBits(-1, 32))); + EXPECT_EQ(IntervalSetTreeToString(qe.GetIntervals(res_pos.node())), + "[[0, 14]]"); +} + TEST_F(ProcStateRangeQueryEngineTest, NegativesWithEq) { auto p = CreatePackage(); ProcBuilder fb(TestName(), p.get()); diff --git a/xls/scheduling/pipeline_schedule.cc b/xls/scheduling/pipeline_schedule.cc index 3e0111a2f0..43fed07797 100644 --- a/xls/scheduling/pipeline_schedule.cc +++ b/xls/scheduling/pipeline_schedule.cc @@ -207,7 +207,8 @@ bool PipelineSchedule::IsLiveOutOfCycle(Node* node, int64_t c) const { } if (user->Is()) { Next* user_next = user->As(); - if (user_next->predicate() != node && user_next->value() != node) { + if (user_next->predicate() != node && user_next->value() != node && + user_next->has_state_read()) { CHECK_EQ(user_next->state_read(), node); // This Next node only uses this StateRead node to target the state // register it needs to write to; it doesn't actually need the value diff --git a/xls/scheduling/pipeline_schedule_test.cc b/xls/scheduling/pipeline_schedule_test.cc index ef11ef44f1..36c6e4e502 100644 --- a/xls/scheduling/pipeline_schedule_test.cc +++ b/xls/scheduling/pipeline_schedule_test.cc @@ -2854,7 +2854,36 @@ TEST_F(PipelineScheduleTest, EXPECT_EQ(schedule.cycle(next.node()), 1); EXPECT_EQ(schedule.cycle(read_mutually_exclusive.node()), 2); } +TEST_F(PipelineScheduleTest, DecoupledNextIsOutOfCycle) { + Package p(TestName()); + ProcBuilder pb("the_proc", &p); + XLS_ASSERT_OK_AND_ASSIGN(StateElement * se, + pb.UnreadStateElement("state", Value(UBits(0, 32)), + /*non_synthesizable=*/false)); + BValue read = pb.StateRead(se); + BValue next = pb.Next(se, read); + XLS_ASSERT_OK_AND_ASSIGN(Proc * proc, pb.Build()); + + // Run scheduler to get a valid base schedule first. + SchedulingOptions options; + options.clock_period_ps(1); + options.pipeline_stages(2); + XLS_ASSERT_OK_AND_ASSIGN( + PipelineSchedule schedule, + RunPipelineSchedule(proc, TestDelayEstimator(), options)); + ScheduleCycleMap cycle_map = schedule.GetCycleMap(); + cycle_map[read.node()] = 0; + cycle_map[next.node()] = 1; + XLS_ASSERT_OK_AND_ASSIGN(schedule, PipelineSchedule::Create(proc, cycle_map)); + EXPECT_EQ(schedule.cycle(read.node()), 0); + EXPECT_EQ(schedule.cycle(next.node()), 1); + // Read is consumed by Next in cycle 1, so read IS live out of cycle 0 + EXPECT_TRUE(schedule.IsLiveOutOfCycle(read.node(), 0)); + // By cycle 1 (where Next is), read no longer requires a pipeline register + // and isn't live out of cycle 1. + EXPECT_FALSE(schedule.IsLiveOutOfCycle(read.node(), 1)); +} TEST_F(PipelineScheduleTest, ProcWriteBeforeReadFailsVerification) { Package p(TestName()); ProcBuilder pb("the_proc", &p); diff --git a/xls/scheduling/schedule_graph.cc b/xls/scheduling/schedule_graph.cc index 7add5389c8..cbde1351a1 100644 --- a/xls/scheduling/schedule_graph.cc +++ b/xls/scheduling/schedule_graph.cc @@ -308,10 +308,13 @@ absl::StatusOr ScheduleGraph::CreateSynchronousGraph( } for (Next* next : proc->next_values()) { - backedges.push_back( - ScheduleBackedge{.source = next, - .destination = next->state_read(), - .distance = LessThanInitiationInterval()}); + backedges.push_back(ScheduleBackedge{ + .source = next, + .destination = + next->has_state_read() + ? next->state_read() + : proc->GetStateReadByStateElement(next->state_element()), + .distance = LessThanInitiationInterval()}); } } diff --git a/xls/tools/delay_info_main_test.py b/xls/tools/delay_info_main_test.py index 2ee4feaa72..717ad46c9b 100644 --- a/xls/tools/delay_info_main_test.py +++ b/xls/tools/delay_info_main_test.py @@ -48,6 +48,15 @@ } } """ +DECOUPLED_PROC_IR = """package decoupled_pkg + +top proc decoupled_proc(__state: bits[32], init={0}) { + sr: bits[32] = state_read(state_element=__state) + one: bits[32] = literal(value=1) + add: bits[32] = add(sr, one) + next_state: () = next_value(state_element=__state, value=add) +} +""" class DelayInfoMainTest(test_base.TestCase): @@ -126,6 +135,25 @@ def test_with_schedule_flag(self): output, ) + def test_decoupled_next_proc(self): + ir_file = self.create_tempfile(content=DECOUPLED_PROC_IR) + output = subprocess.check_output([ + DELAY_INFO_MAIN_PATH, + '--delay_model=unit', + '--schedule', + '--pipeline_stages=1', + ir_file.full_path, + ]).decode('utf-8') + print(output) + self.assertIn('# Critical path for state element __state', output) + self.assertIn( + '__state: bits[32] = state_read(state_element=__state,', output + ) + self.assertIn( + 'add: bits[32] = add(__state: bits[32], one: bits[32],', output + ) + self.assertIn('next_state: () = next_value(state_element=__state,', output) + if __name__ == '__main__': test_base.main() diff --git a/xls/tools/delay_info_printer.cc b/xls/tools/delay_info_printer.cc index 91ecf69270..14edf76e25 100644 --- a/xls/tools/delay_info_printer.cc +++ b/xls/tools/delay_info_printer.cc @@ -255,8 +255,12 @@ class DelayInfoPrinterImpl : public DelayInfoPrinter { return true; } if (node->Is()) { - return node->As()->state_read() == - proc->GetStateReadByStateElement(state_element); + Next* next = node->As(); + return next->has_state_read() + ? next->state_read() == + proc->GetStateReadByStateElement( + state_element) + : next->state_element() == state_element; } return false; }));