Skip to content

Commit 5f98e3d

Browse files
allightcopybara-github
authored andcommitted
Treat MinDelay operations as explicit scheduling constraints in ASAP scheduler.
MinDelay nodes are now converted into NodeDifferenceConstraints and added to the constraint set. This removes the need to handle MinDelay specially within the lower bound tightening loop, simplifying the bound calculation and avoiding potential bugs where operands aren't pulled forward. PiperOrigin-RevId: 916274884
1 parent 7eacef4 commit 5f98e3d

3 files changed

Lines changed: 154 additions & 5 deletions

File tree

xls/scheduling/BUILD

Lines changed: 1 addition & 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",

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)