Skip to content

Commit f25c1b0

Browse files
allightcopybara-github
authored andcommitted
Avoid adding cycle constraints for untimed nodes in pipeline scheduling.
Untimed nodes (e.g., literal) do not have a specific cycle and should not be subject to NodeInCycleConstraints. This change prevents adding such constraints for untimed nodes during pipeline scheduling. PiperOrigin-RevId: 916141903
1 parent 957d6fc commit f25c1b0

5 files changed

Lines changed: 177 additions & 11 deletions

File tree

xls/scheduling/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ cc_test(
348348
deps = [
349349
":schedule_bounds",
350350
":schedule_graph",
351+
":scheduling_options",
351352
"//xls/common:xls_gunit_main",
352353
"//xls/common/status:matchers",
353354
"//xls/common/status:status_macros",
@@ -691,6 +692,7 @@ cc_library(
691692
deps = [
692693
":pipeline_schedule",
693694
":run_pipeline_schedule",
695+
":schedule_graph",
694696
":scheduling_options",
695697
":scheduling_pass",
696698
"//xls/common/status:ret_check",

xls/scheduling/pipeline_schedule.cc

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,17 +265,32 @@ absl::Status PipelineSchedule::Verify() const {
265265
continue;
266266
}
267267
XLS_RET_CHECK(IsScheduled(node));
268+
if (node->Is<MinDelay>()) {
269+
// NB Our SDC scheduler will usually put the min-delay after its operand
270+
// but the ASAP and potentially other schedulers (due to the specific way
271+
// they represent delay and the constraints the min-delay node represents)
272+
// may place it in the same cycle as the operand or even place it
273+
// somewhere in between the operand and the nodes users. To allow for any
274+
// choice we check the difference between the users and the operand
275+
// directly and don't care about the min-delay itself.
276+
int64_t cycle_operand = cycle(node->operand(0));
277+
for (Node* user : node->users()) {
278+
XLS_RET_CHECK_LE(cycle_operand,
279+
cycle(user) - node->As<MinDelay>()->delay())
280+
<< "The user '" << user->ToString() << "' at cycle " << cycle(user)
281+
<< " of the MinDelay node " << node
282+
<< " is not correctly scheduled. It is not "
283+
<< node->As<MinDelay>()->delay()
284+
<< " cycles after the min-delay operand " << node->operand(0)
285+
<< " at cycle " << cycle_operand << ".";
286+
}
287+
}
268288
for (Node* operand : node->operands()) {
269289
if (IsUntimed(operand)) {
270290
continue;
271291
}
272292
XLS_RET_CHECK_LE(cycle(operand), cycle(node))
273293
<< "Operand " << operand << " scheduled after user " << node;
274-
275-
if (node->Is<MinDelay>()) {
276-
XLS_RET_CHECK_LE(cycle(operand),
277-
cycle(node) - node->As<MinDelay>()->delay());
278-
}
279294
}
280295
}
281296
if (function_base()->IsProc()) {

xls/scheduling/pipeline_scheduling_pass.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "xls/passes/pass_base.h"
3333
#include "xls/scheduling/pipeline_schedule.h"
3434
#include "xls/scheduling/run_pipeline_schedule.h"
35+
#include "xls/scheduling/schedule_graph.h"
3536
#include "xls/scheduling/scheduling_options.h"
3637
#include "xls/scheduling/scheduling_pass.h"
3738

@@ -61,7 +62,7 @@ absl::Status AddCycleConstraints(const PipelineSchedule& schedule,
6162
}
6263
}
6364
}
64-
if (!already_constrained) {
65+
if (!already_constrained && !IsUntimed(node)) {
6566
scheduling_options.add_constraint(NodeInCycleConstraint(node, c));
6667
}
6768
}

xls/scheduling/schedule_bounds.cc

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ class ConstraintConverter {
8888
constraint);
8989
}
9090

91+
void AddNodeConstraint(const NodeSchedulingConstraint& constraint) {
92+
node_constraints_.push_back(constraint);
93+
}
94+
9195
absl::Status AddSpecificConstraint(const NodeInCycleConstraint& nic) {
9296
node_constraints_.emplace_back(nic);
9397
return absl::OkStatus();
@@ -381,6 +385,18 @@ ScheduleBounds::ConvertSchedulingConstraints(
381385
XLS_RETURN_IF_ERROR(converter.AddConstraint(constraint))
382386
<< "Could not add constraint for " << graph.name();
383387
}
388+
// Finally add any min-delay constraints as a final pass.
389+
for (const ScheduleNode& node : graph.nodes()) {
390+
if (node.node->Is<MinDelay>()) {
391+
converter.AddNodeConstraint(
392+
NodeSchedulingConstraint(NodeDifferenceConstraint{
393+
.anchor = node.node->operand(0),
394+
.subject = node.node,
395+
.min_after = node.node->As<MinDelay>()->delay(),
396+
.max_after = max_upper_bound,
397+
}));
398+
}
399+
}
384400
XLS_ASSIGN_OR_RETURN(std::vector<NodeSchedulingConstraint> node_constraints,
385401
std::move(converter).Finalize());
386402
VLOG(2) << " Node constraints are: ["
@@ -557,14 +573,13 @@ absl::StatusOr<std::optional<int64_t>> PropagateGenericBounds(
557573
node_in_cycle_delay = in_cycle_delay.at(operand.node) + operand_delay;
558574
continue;
559575
}
560-
int64_t min_delay = operand.node->Is<MinDelay>()
561-
? operand.node->As<MinDelay>()->delay()
562-
: 0;
563-
if (operand_cb + min_delay > node_cb) {
576+
// NB MinDelay nodes are handled by constraints so no need to do anything
577+
// here.
578+
if (operand_cb > node_cb) {
564579
VLOG(4) << absl::StreamFormat(
565580
" tightened lb to %d because of operand %s", operand_cb,
566581
operand.node->GetName());
567-
XLS_RETURN_IF_ERROR(TightenBound(node_cb, operand_cb + min_delay));
582+
XLS_RETURN_IF_ERROR(TightenBound(node_cb, operand_cb));
568583
changed = true;
569584
node_in_cycle_delay = 0;
570585
continue;

xls/scheduling/schedule_bounds_test.cc

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "xls/ir/source_location.h"
4343
#include "xls/ir/value.h"
4444
#include "xls/scheduling/schedule_graph.h"
45+
#include "xls/scheduling/scheduling_options.h"
4546

4647
namespace m = xls::op_matchers;
4748
namespace xls {
@@ -502,6 +503,138 @@ TEST_F(ScheduleBoundsTest, PushNodeLaterChain) {
502503
EXPECT_THAT(sched.bounds(h.node()), Bounds(7, 7));
503504
}
504505

506+
TEST_F(ScheduleBoundsTest, MinDelayBasic) {
507+
auto p = CreatePackage();
508+
FunctionBuilder fb(TestName(), p.get());
509+
auto a = fb.Param("a", p->GetBitsType(32));
510+
auto b = fb.Param("b", p->GetBitsType(32));
511+
fb.Add(a, b);
512+
auto tkn = fb.Literal(Value::Token());
513+
auto min_delay = fb.MinDelay(tkn, /*delay=*/3);
514+
auto assert = fb.Assert(min_delay, fb.Literal(UBits(1, 1)), "msg");
515+
XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build());
516+
517+
ControllableDelayEstimator delay;
518+
delay.SetDelay(m::Add(), 1);
519+
delay.SetDelay(Op::kMinDelay, 0);
520+
delay.SetDelay(Op::kAssert, 0);
521+
delay.SetDelay(FreeOperations(), 0);
522+
523+
XLS_ASSERT_OK_AND_ASSIGN(
524+
ScheduleBounds sched,
525+
ScheduleBounds::ComputeAsapAndAlapBounds(f,
526+
/*clock_period_ps=*/3, delay));
527+
EXPECT_THAT(sched.bounds(min_delay.node()), Bounds(3, 3));
528+
EXPECT_THAT(sched.bounds(assert.node()), Bounds(3, 3));
529+
}
530+
531+
TEST_F(ScheduleBoundsTest, MinDelayChain) {
532+
auto p = CreatePackage();
533+
FunctionBuilder fb(TestName(), p.get());
534+
auto tkn = fb.Literal(Value::Token());
535+
auto md1 = fb.MinDelay(tkn, /*delay=*/2);
536+
auto md2 = fb.MinDelay(md1, /*delay=*/3);
537+
fb.Assert(md2, fb.Literal(UBits(1, 1)), "msg");
538+
XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build());
539+
540+
ControllableDelayEstimator delay;
541+
delay.SetDelay(Op::kMinDelay, 0);
542+
delay.SetDelay(Op::kAssert, 0);
543+
delay.SetDelay(FreeOperations(), 0);
544+
545+
XLS_ASSERT_OK_AND_ASSIGN(
546+
ScheduleBounds sched,
547+
ScheduleBounds::ComputeAsapAndAlapBounds(f,
548+
/*clock_period_ps=*/3, delay));
549+
EXPECT_THAT(sched.bounds(md1.node()), Bounds(2, 2));
550+
EXPECT_THAT(sched.bounds(md2.node()), Bounds(5, 5));
551+
}
552+
553+
TEST_F(ScheduleBoundsTest, MinDelayAndIIInteraction) {
554+
auto p = CreatePackage();
555+
ProcBuilder pb(NewStyleProc(), TestName(), p.get());
556+
XLS_ASSERT_OK_AND_ASSIGN(ReceiveChannelInterface * ch_in,
557+
pb.AddInputChannel("in", p->GetBitsType(32)));
558+
XLS_ASSERT_OK_AND_ASSIGN(SendChannelInterface * ch_out,
559+
pb.AddOutputChannel("out", p->GetBitsType(32)));
560+
561+
BValue state = pb.StateElement("st", Value(UBits(0, 32)));
562+
BValue tkn = pb.Literal(Value::Token());
563+
BValue rcv = pb.Receive(ch_in, tkn);
564+
BValue rcv_tkn = pb.TupleIndex(rcv, 0);
565+
BValue rcv_data = pb.TupleIndex(rcv, 1);
566+
BValue md = pb.MinDelay(rcv_tkn, /*delay=*/3);
567+
BValue send = pb.Send(ch_out, md, rcv_data);
568+
// Make the next state depend on send token so backedge constraint covers the
569+
// whole chain.
570+
BValue next_state = pb.Add(state, pb.TupleIndex(pb.Receive(ch_in, send), 1));
571+
pb.Next(state, next_state);
572+
XLS_ASSERT_OK_AND_ASSIGN(Proc * proc, pb.Build());
573+
574+
ControllableDelayEstimator delay;
575+
delay.SetDelay(m::Add(), 1);
576+
delay.SetDelay(Op::kMinDelay, 0);
577+
delay.SetDelay(FreeOperations(), 0);
578+
579+
// Set II = 4.
580+
// Backedge constraint: Next - state <= II - 1 = 3.
581+
// MinDelay constraint: send >= md >= rcv_tkn + 3.
582+
// Since rcv_tkn is derived from tkn (state read), this forces send to be
583+
// scheduled at least 3 cycles after the receive.
584+
XLS_ASSERT_OK_AND_ASSIGN(
585+
ScheduleBounds sched,
586+
ScheduleBounds::ComputeAsapAndAlapBounds(
587+
proc,
588+
/*clock_period_ps=*/3, delay, /*ii=*/4, {BackedgeConstraint()}));
589+
EXPECT_THAT(sched.bounds(md.node()), Bounds(3, 3));
590+
EXPECT_THAT(sched.bounds(send.node()), Bounds(3, 3));
591+
}
592+
593+
TEST_F(ScheduleBoundsTest, MinDelayAndIOConstraintConflict) {
594+
auto p = CreatePackage();
595+
ProcBuilder pb(NewStyleProc(), TestName(), p.get());
596+
XLS_ASSERT_OK_AND_ASSIGN(ReceiveChannelInterface * ch_a,
597+
pb.AddInputChannel("a", p->GetBitsType(32)));
598+
XLS_ASSERT_OK_AND_ASSIGN(SendChannelInterface * ch_b,
599+
pb.AddOutputChannel("b", p->GetBitsType(32)));
600+
XLS_ASSERT_OK_AND_ASSIGN(SendChannelInterface * ch_c,
601+
pb.AddOutputChannel("c", p->GetBitsType(32)));
602+
603+
BValue tkn = pb.Literal(Value::Token());
604+
BValue rcv_a = pb.Receive(ch_a, tkn);
605+
BValue rcv_a_tkn = pb.TupleIndex(rcv_a, 0);
606+
BValue rcv_a_data = pb.TupleIndex(rcv_a, 1);
607+
608+
BValue md1 = pb.MinDelay(rcv_a_tkn, /*delay=*/2);
609+
BValue send_b = pb.Send(ch_b, md1, rcv_a_data);
610+
611+
BValue md2 = pb.MinDelay(send_b, /*delay=*/2);
612+
pb.Send(ch_c, md2, rcv_a_data);
613+
614+
XLS_ASSERT_OK_AND_ASSIGN(Proc * proc, pb.Build());
615+
616+
ControllableDelayEstimator delay;
617+
delay.SetDelay(Op::kMinDelay, 0);
618+
delay.SetDelay(FreeOperations(), 0);
619+
620+
// IO constraint says send_c (Channel "c") must be at most 3 cycles after
621+
// rcv_a (Channel "a"). MinDelay requires:
622+
// send_b >= rcv_a + 2
623+
// send_c >= send_b + 2
624+
// So send_c >= rcv_a + 4.
625+
// This conflicts with maximum_latency = 3.
626+
IOConstraint io_const("a", IODirection::kReceive, "c", IODirection::kSend,
627+
/*minimum_latency=*/0, /*maximum_latency=*/3);
628+
629+
EXPECT_THAT(
630+
ScheduleBounds::ComputeAsapAndAlapBounds(proc,
631+
/*clock_period_ps=*/3, delay,
632+
/*ii=*/std::nullopt, {io_const}),
633+
StatusIs(absl::StatusCode::kInternal,
634+
ContainsRegex(".*failed to converge.*|.*potentially "
635+
"incompatible with constraint.*")));
636+
}
637+
505638
TEST_F(ScheduleBoundsTest, StringifyConstraints) {
506639
auto p = CreatePackage();
507640
FunctionBuilder fb(TestName(), p.get());

0 commit comments

Comments
 (0)