Skip to content

Commit eaba202

Browse files
Merge pull request #3207 from xlsynth:meheff/10-16-2025-array-update-generate
PiperOrigin-RevId: 826493478
2 parents d55d14c + 5d05563 commit eaba202

File tree

49 files changed

+872
-412
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+872
-412
lines changed

xls/codegen/block_generator.cc

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -527,13 +527,63 @@ class BlockGenerator {
527527
return false;
528528
}
529529

530+
// Returns whether the given operand appears multiple times in the emitted
531+
// verilog expression/statement(s) for the given node.
532+
bool OperandAppearsMultipleTimesInExpression(Node* n, int64_t operand_no) {
533+
switch (n->op()) {
534+
case Op::kArraySlice:
535+
case Op::kArrayUpdate:
536+
case Op::kArrayConcat:
537+
return true;
538+
// Select operations have multiple comparisons to the selector. Priority
539+
// select is emitted as a separate function and doesn't have this
540+
// property.
541+
case Op::kSel:
542+
return n->As<Select>()->cases().size() > 2 &&
543+
operand_no == Select::kSelectorOperand;
544+
case Op::kOneHotSel:
545+
return operand_no == OneHotSelect::kSelectorOperand;
546+
case Op::kEncode:
547+
case Op::kReverse:
548+
return true;
549+
case Op::kEq:
550+
case Op::kNe:
551+
return !n->GetType()->IsBits();
552+
case Op::kAssert:
553+
return true;
554+
default:
555+
return false;
556+
}
557+
}
558+
559+
// Returns whether the expression for the given node will be duplicated if
560+
// inlined into its uses.
561+
bool InlinedExpressionWillBeDuplicated(Node* n) {
562+
if (n->users().size() == 0) {
563+
return false;
564+
} else if (n->users().size() > 1) {
565+
return true;
566+
}
567+
Node* user = n->users().front();
568+
std::optional<int64_t> operand_no;
569+
for (int64_t i = 0; i < user->operand_count(); i++) {
570+
if (user->operand(i) == n) {
571+
if (operand_no.has_value()) {
572+
return true;
573+
}
574+
operand_no = i;
575+
}
576+
}
577+
return OperandAppearsMultipleTimesInExpression(user, operand_no.value());
578+
}
579+
530580
// Returns true if the given node should be emitted as an assignment
531581
// for readability purposes. This is a soft constraint / suggestion.
532582
bool ShouldEmitAsAssignment(Node* const n, int64_t inline_depth) {
533-
return (
534-
n->HasAssignedName() ||
535-
(n->users().size() > 1 && !ShouldInlineExpressionIntoMultipleUses(n)) ||
536-
inline_depth > options_.max_inline_depth());
583+
return (n->HasAssignedName() ||
584+
(InlinedExpressionWillBeDuplicated(n) &&
585+
!ShouldInlineExpressionIntoMultipleUses(n)) ||
586+
inline_depth > options_.max_inline_depth());
537587
}
538588

539589
// Name of the node if it gets emitted as a separate assignment.
@@ -906,8 +956,8 @@ class BlockGenerator {
906956
// Emit each instantiation in the block into the separate instantiation module
907957
// section.
908958
absl::Status EmitInstantiations() {
909-
// Since instantiations are emitted at the end, and not the pipeline stages
910-
// they are in, separate with headline to reduce confusion.
959+
// Since instantiations are emitted at the end, and not the pipeline
960+
// stages they are in, separate with headline to reduce confusion.
911961
if (!block_->GetInstantiations().empty()) {
912962
mb_.declaration_section()->Add<BlankLine>(SourceInfo());
913963
mb_.instantiation_section()->Add<Comment>(SourceInfo(),

xls/codegen/module_builder.cc

Lines changed: 100 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -645,186 +645,103 @@ absl::StatusOr<Expression*> ModuleBuilder::EmitAsInlineExpression(
645645
return NodeToExpression(node, inputs, file_, options_);
646646
}
647647

648-
absl::Status ModuleBuilder::EmitArrayCopyAndUpdateViaGenerate1D(
648+
absl::Status ModuleBuilder::EmitArrayCopyAndUpdateViaGenerate(
649649
std::string_view op_name, IndexableExpression* lhs,
650-
IndexableExpression* rhs, Expression* update_value, IndexType index_type,
651-
Type* xls_type) {
652-
ArrayType* array_type = xls_type->AsArrayOrDie();
653-
654-
// Create names derived from the unique operation name to ensure the derived
655-
// names are unique. We namespace via double underscore in an attempt to
656-
// reserve the suffixes and avoid collisions.
657-
std::string genvar_name =
658-
SanitizeAndUniquifyName(absl::StrCat(op_name, "__index"));
659-
std::string generate_loop_name =
660-
SanitizeAndUniquifyName(absl::StrCat(op_name, "__gen"));
661-
662-
XLS_ASSIGN_OR_RETURN(auto* genvar,
663-
module_->AddGenvar(genvar_name, SourceInfo()));
664-
665-
GenerateLoop* generate_loop = module_->Add<GenerateLoop>(
666-
SourceInfo(), genvar, file_->PlainLiteral(0, SourceInfo()),
667-
file_->PlainLiteral(array_type->size(), SourceInfo()),
668-
generate_loop_name);
669-
670-
// In the body there is a conditional where we choose to either assign the rhs
671-
// index directly or the updated value.
672-
Expression* index_is_match =
673-
file_->Equals(index_type.expression, genvar, SourceInfo());
674-
Expression* rhs_at_index = file_->Index(rhs, genvar, SourceInfo());
675-
generate_loop->AddBodyNode(file_->Make<ContinuousAssignment>(
676-
SourceInfo(), file_->Make<Index>(SourceInfo(), lhs, genvar),
677-
file_->Ternary(index_is_match, update_value, rhs_at_index,
678-
SourceInfo())));
679-
return absl::OkStatus();
680-
}
681-
682-
// Emits a copy and update of an array as a sequence of assignments.
683-
// Specifically, 'rhs' is copied to 'lhs' with the element at the indices
684-
// 'indices' replaced with 'update_value'. Examples of emitted verilog:
685-
//
686-
// // lhs: bits[32][3] = array_update(rhs, value, indices=[1]):
687-
// assign lhs[0] = rhs[0];
688-
// assign lhs[1] = value;
689-
// assign lhs[2] = rhs[1];
690-
//
691-
// // lhs: bits[32][3] = array_update(rhs, value, indices=[x]):
692-
// assign lhs[0] = x == 0 ? value : rhs[0];
693-
// assign lhs[1] = x == 1 ? value : rhs[1];
694-
// assign lhs[2] = x == 2 ? value : rhs[2];
695-
//
696-
// // lhs: bits[32][3][2] = array_update(rhs, value, indices=[x, 1]):
697-
// assign lhs[0][0] = rhs[0][0];
698-
// assign lhs[0][1] = x == 0 ? value : rhs[0][1];
699-
// assign lhs[0][2] = rhs[0][2];
700-
// assign lhs[1][0] = rhs[1][0];
701-
// assign lhs[1][1] = x == 1 ? value : rhs[0][1];
702-
// assign lhs[1][2] = rhs[0][2];
703-
//
704-
// // lhs: bits[32][3][2] = array_update(rhs, value, indices=[x, y]):
705-
// assign lhs[0][0] = (x == 0 && y == 0) ? value : rhs[0][0];
706-
// assign lhs[0][1] = (x == 0 && y == 1) ? value : rhs[0][1];
707-
// assign lhs[0][2] = (x == 0 && y == 2) ? value : rhs[0][2];
708-
// assign lhs[1][0] = (x == 1 && y == 0) ? value : rhs[1][0];
709-
// assign lhs[1][1] = (x == 1 && y == 1) ? value : rhs[1][1];
710-
// assign lhs[1][2] = (x == 1 && y == 2) ? value : rhs[1][2];
711-
//
712-
// // lhs: bits[32][3][2] = array_update(rhs, value, indices=[x]):
713-
// assign lhs[0][0] = x == 0 ? value[0] : rhs[0][0];
714-
// assign lhs[0][1] = x == 0 ? value[1] : rhs[0][1];
715-
// assign lhs[0][2] = x == 0 ? value[2] : rhs[0][2];
716-
// assign lhs[1][0] = x == 1 ? value[0] : rhs[1][0];
717-
// assign lhs[1][1] = x == 1 ? value[1] : rhs[1][1];
718-
// assign lhs[1][2] = x == 1 ? value[2] : rhs[1][2];
719-
//
720-
// EmitArrayCopyAndUpdate recursively constructs the assignments. 'index_match'
721-
// is the index match expression (or explicit true/false value) used in ternary
722-
// expression to select element(s) from the update value or the rhs.
723-
absl::Status ModuleBuilder::EmitArrayCopyAndUpdate(
724-
IndexableExpression* lhs, IndexableExpression* rhs,
725-
Expression* update_value,
726-
absl::Span<const ModuleBuilder::IndexType> indices, IndexMatch index_match,
727-
Type* xls_type) {
728-
auto is_statically_true = [](const IndexMatch& im) {
729-
return std::holds_alternative<bool>(im) && std::get<bool>(im);
730-
};
731-
auto is_statically_false = [](const IndexMatch& im) {
732-
return std::holds_alternative<bool>(im) && !std::get<bool>(im);
733-
};
734-
auto combine_index_matches = [&](const IndexMatch& a,
735-
const IndexMatch& b) -> IndexMatch {
736-
if (is_statically_false(a) || is_statically_false(b)) {
737-
return false;
650+
IndexableExpression* rhs, Expression* update_value,
651+
absl::Span<const ModuleBuilder::IndexType> indices, Type* xls_type) {
652+
std::vector<GenerateLoopIterationSpec> iteration_specs;
653+
// Genvars for the index space of `indices`.
654+
std::vector<LogicRef*> index_genvars;
655+
// Genvars for the index space of the updated value. This will be non-empty
656+
// only if the update value is an array.
657+
std::vector<LogicRef*> update_genvars;
658+
659+
// Create the iteration space for `indices`.
660+
Type* iter_type = xls_type;
661+
for (int64_t dim_index = 0; dim_index < indices.size(); ++dim_index) {
662+
ArrayType* current_array = iter_type->AsArrayOrDie();
663+
int64_t dim_size = current_array->size();
664+
665+
std::string genvar_name =
666+
SanitizeAndUniquifyName(absl::StrCat(op_name, "__index_", dim_index));
667+
XLS_ASSIGN_OR_RETURN(LogicRef * genvar,
668+
module_->AddGenvar(genvar_name, SourceInfo()));
669+
index_genvars.push_back(genvar);
670+
iteration_specs.push_back(GenerateLoopIterationSpec{
671+
genvar, file_->PlainLiteral(0, SourceInfo()),
672+
file_->PlainLiteral(dim_size, SourceInfo()),
673+
std::optional<std::string>(SanitizeAndUniquifyName(
674+
absl::StrCat(op_name, "__gen_", dim_index)))});
675+
676+
iter_type = current_array->element_type();
677+
}
678+
679+
// Create the iteration space for the update value.
680+
Type* update_leaf_type = iter_type;
681+
int64_t update_dim = 0;
682+
while (update_leaf_type->IsArray()) {
683+
ArrayType* current_array = update_leaf_type->AsArrayOrDie();
684+
int64_t dim_size = current_array->size();
685+
686+
std::string genvar_name =
687+
SanitizeAndUniquifyName(absl::StrCat(op_name, "__update_", update_dim));
688+
XLS_ASSIGN_OR_RETURN(LogicRef * genvar,
689+
module_->AddGenvar(genvar_name, SourceInfo()));
690+
update_genvars.push_back(genvar);
691+
iteration_specs.push_back(GenerateLoopIterationSpec{
692+
genvar, file_->PlainLiteral(0, SourceInfo()),
693+
file_->PlainLiteral(dim_size, SourceInfo()),
694+
std::optional<std::string>(SanitizeAndUniquifyName(
695+
absl::StrCat(op_name, "__gen_up_", update_dim)))});
696+
697+
update_leaf_type = current_array->element_type();
698+
++update_dim;
699+
}
700+
701+
GenerateLoop* generate_loop =
702+
module_->Add<GenerateLoop>(SourceInfo(), iteration_specs);
703+
704+
// Build the index match condition.
705+
Expression* indices_match = nullptr;
706+
for (int64_t i = 0; i < indices.size(); ++i) {
707+
Expression* eq =
708+
file_->Equals(indices[i].expression, index_genvars[i], SourceInfo());
709+
indices_match = (indices_match == nullptr)
710+
? eq
711+
: file_->LogicalAnd(indices_match, eq, SourceInfo());
712+
}
713+
714+
// Build lhs and rhs indexed by all loop genvars (index + update dims).
715+
IndexableExpression* lhs_indexed = lhs;
716+
IndexableExpression* rhs_indexed = rhs;
717+
for (LogicRef* v : index_genvars) {
718+
lhs_indexed = file_->Index(lhs_indexed, v, SourceInfo());
719+
rhs_indexed = file_->Index(rhs_indexed, v, SourceInfo());
720+
}
721+
for (LogicRef* v : update_genvars) {
722+
lhs_indexed = file_->Index(lhs_indexed, v, SourceInfo());
723+
rhs_indexed = file_->Index(rhs_indexed, v, SourceInfo());
724+
}
725+
726+
// Build update_value indexed by its own loop genvars.
727+
Expression* update_indexed = update_value;
728+
if (!update_genvars.empty()) {
729+
IndexableExpression* update_idx =
730+
update_value->AsIndexableExpressionOrDie();
731+
for (LogicRef* v : update_genvars) {
732+
update_idx = file_->Index(update_idx, v, SourceInfo());
738733
}
739-
if (is_statically_true(a)) {
740-
return b;
741-
}
742-
if (is_statically_true(b)) {
743-
return a;
744-
}
745-
return file_->LogicalAnd(std::get<Expression*>(a), std::get<Expression*>(b),
746-
SourceInfo());
747-
};
748-
749-
if (indices.empty()) {
750-
if (is_statically_true(index_match)) {
751-
// Indices definitely *do* match the subarray/element being replaced with
752-
// update value. Assign from update value exclusively. E.g.:
753-
// assign lhs[i][j] = update_value[j]
754-
return AddAssignment(
755-
/*xls_type=*/xls_type,
756-
/*lhs=*/lhs,
757-
/*rhs=*/update_value,
758-
/*add_assignment=*/
759-
[&](Expression* lhs, Expression* rhs) {
760-
assignment_section()->Add<ContinuousAssignment>(SourceInfo(), lhs,
761-
rhs);
762-
});
763-
}
764-
if (is_statically_false(index_match)) {
765-
// Indices definitely do *NOT* match the subarray/element being replaced
766-
// with update value. Assign from rhs exclusively. E.g.:
767-
// assign lhs[i][j] = rhs[j]
768-
return AddAssignment(
769-
/*xls_type=*/xls_type,
770-
/*lhs=*/lhs,
771-
/*rhs=*/rhs,
772-
/*add_assignment=*/
773-
[&](Expression* lhs, Expression* rhs) {
774-
assignment_section()->Add<ContinuousAssignment>(SourceInfo(), lhs,
775-
rhs);
776-
});
777-
}
778-
// Indices may or may not match the subarray/element being replaced with
779-
// update value. Use a ternary expression to pick from rhs or update
780-
// value. E.g:
781-
// assign lhs[i][j] = (i == idx) ? update_value[j] : rhs[j]
782-
auto gen_ternary = [&](absl::Span<Expression* const> inputs) {
783-
return file_->Ternary(std::get<Expression*>(index_match), inputs[0],
784-
inputs[1], SourceInfo());
785-
};
786-
787-
// Emit a continuous assignment with a ternary select. The ternary
788-
// operation supports array types in SystemVerilog so sv_array_expr is
789-
// true.
790-
return AddAssignmentToGeneratedExpression(
791-
xls_type, lhs, /*inputs=*/{update_value, rhs}, gen_ternary,
792-
/*add_assignment=*/
793-
[&](Expression* lhs, Expression* rhs) {
794-
assignment_section()->Add<ContinuousAssignment>(SourceInfo(), lhs,
795-
rhs);
796-
},
797-
/*sv_array_expr=*/true);
798-
}
799-
800-
// Iterate through array elements and recurse.
801-
ArrayType* array_type = xls_type->AsArrayOrDie();
802-
IndexType index_type = indices.front();
803-
int64_t index_bit_count = index_type.xls_type->bit_count();
804-
for (int64_t i = 0; i < array_type->size(); ++i) {
805-
// Compute the current index match expression for this index element.
806-
IndexMatch current_index_match;
807-
if (index_type.expression->IsLiteral()) {
808-
// Index element is a literal. The condition is statically known (true or
809-
// false).
810-
current_index_match = index_type.expression->IsLiteralWithValue(i);
811-
} else if (Bits::MinBitCountUnsigned(i) > index_bit_count) {
812-
// Index is not wide enough to hold 'i'.
813-
current_index_match = false;
814-
} else {
815-
// Index element is a not literal. The condition is not statically known.
816-
current_index_match =
817-
file_->Equals(index_type.expression,
818-
file_->Literal(UBits(i, index_bit_count), SourceInfo()),
819-
SourceInfo());
820-
}
821-
XLS_RETURN_IF_ERROR(EmitArrayCopyAndUpdate(
822-
file_->Index(lhs, i, SourceInfo()), file_->Index(rhs, i, SourceInfo()),
823-
update_value, indices.subspan(1),
824-
combine_index_matches(current_index_match, index_match),
825-
array_type->element_type()));
734+
update_indexed = update_idx;
826735
}
827736

737+
// Emit the assignment inside the generate loop body.
738+
Expression* assignment_rhs =
739+
indices_match == nullptr ? update_indexed
740+
: file_->Ternary(indices_match, update_indexed,
741+
rhs_indexed, SourceInfo());
742+
generate_loop->AddStatement(file_->Make<ContinuousAssignment>(
743+
SourceInfo(), lhs_indexed, assignment_rhs));
744+
828745
return absl::OkStatus();
829746
}
830747

@@ -965,26 +882,13 @@ absl::StatusOr<LogicRef*> ModuleBuilder::EmitAsAssignment(
965882
IndexType{inputs[i], node->operand(i)->GetType()->AsBitsOrDie()});
966883
}
967884

968-
// We use a generate loop in the simple case where the array is 1D and
969-
// the element type is bits.
970-
if (index_types.size() == 1 &&
971-
node->GetType()->AsArrayOrDie()->element_type()->IsBits()) {
972-
XLS_RETURN_IF_ERROR(EmitArrayCopyAndUpdateViaGenerate1D(
973-
/*op_name=*/node->GetName(),
974-
/*lhs=*/ref,
975-
/*rhs=*/inputs[0]->AsIndexableExpressionOrDie(),
976-
/*update_value=*/inputs[1],
977-
/*index_type=*/index_types[0],
978-
/*xls_type=*/array_type));
979-
} else {
980-
XLS_RETURN_IF_ERROR(EmitArrayCopyAndUpdate(
981-
/*lhs=*/ref,
982-
/*rhs=*/inputs[0]->AsIndexableExpressionOrDie(),
983-
/*update_value=*/inputs[1],
984-
/*indices=*/index_types,
985-
/*index_match=*/true,
986-
/*xls_type=*/array_type));
987-
}
885+
XLS_RETURN_IF_ERROR(EmitArrayCopyAndUpdateViaGenerate(
886+
/*op_name=*/node->GetName(),
887+
/*lhs=*/ref,
888+
/*rhs=*/inputs[0]->AsIndexableExpressionOrDie(),
889+
/*update_value=*/inputs[1],
890+
/*indices=*/index_types,
891+
/*xls_type=*/array_type));
988892
break;
989893
}
990894
case Op::kArrayConcat: {

0 commit comments

Comments
 (0)