diff --git a/xls/codegen_v_1_5/BUILD b/xls/codegen_v_1_5/BUILD index 2cceef3c91..c04fcc1efb 100644 --- a/xls/codegen_v_1_5/BUILD +++ b/xls/codegen_v_1_5/BUILD @@ -41,15 +41,19 @@ cc_library( hdrs = ["block_conversion_pass_pipeline.h"], deps = [ ":block_conversion_pass", + ":block_conversion_wrapper_pass", ":block_finalization_pass", ":channel_to_port_io_lowering_pass", ":flow_control_insertion_pass", ":function_io_lowering_pass", ":idle_insertion_pass", ":pipeline_register_insertion_pass", + ":register_cleanup_pass", ":scheduled_block_conversion_pass", ":scheduling_pass", ":state_to_register_io_lowering_pass", + "//xls/passes:dce_pass", + "//xls/passes:optimization_pass", ], ) @@ -93,6 +97,65 @@ cc_test( ], ) +cc_library( + name = "register_cleanup_pass", + srcs = ["register_cleanup_pass.cc"], + hdrs = ["register_cleanup_pass.h"], + deps = [ + ":block_conversion_pass", + "//xls/common/status:status_macros", + "//xls/data_structures:transitive_closure", + "//xls/ir", + "//xls/ir:register", + "//xls/ir:value", + "//xls/ir:value_utils", + "//xls/passes:node_dependency_analysis", + "//xls/passes:partial_info_query_engine", + "//xls/passes:pass_base", + "//xls/passes:query_engine", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/types:span", + ], +) + +cc_library( + name = "block_conversion_wrapper_pass", + srcs = ["block_conversion_wrapper_pass.cc"], + hdrs = ["block_conversion_wrapper_pass.h"], + deps = [ + ":block_conversion_pass", + "//xls/ir", + "//xls/passes:optimization_pass", + "//xls/passes:pass_base", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:span", + ], +) + +cc_test( + name = "block_conversion_wrapper_pass_test", + srcs = ["block_conversion_wrapper_pass_test.cc"], + deps = [ + ":block_conversion_pass", + ":block_conversion_wrapper_pass", + "//xls/common:xls_gunit_main", + "//xls/common/status:matchers", + "//xls/ir", + "//xls/ir:function_builder", + "//xls/ir:ir_matcher", + "//xls/ir:ir_test_base", + "//xls/passes:dce_pass", + "//xls/passes:optimization_pass", + "//xls/passes:pass_base", + "@com_google_absl//absl/status:statusor", + "@googletest//:gtest", + ], +) + cc_library( name = "channel_to_port_io_lowering_pass", srcs = ["channel_to_port_io_lowering_pass.cc"], @@ -170,6 +233,7 @@ cc_library( "//xls/common/status:status_macros", "//xls/estimators/delay_model:delay_estimator", "//xls/ir", + "//xls/passes:optimization_pass", "//xls/passes:pass_base", "//xls/scheduling:pipeline_schedule_cc_proto", "//xls/scheduling:scheduling_options", diff --git a/xls/codegen_v_1_5/block_conversion_pass_pipeline.cc b/xls/codegen_v_1_5/block_conversion_pass_pipeline.cc index ea3f42c842..d746c46499 100644 --- a/xls/codegen_v_1_5/block_conversion_pass_pipeline.cc +++ b/xls/codegen_v_1_5/block_conversion_pass_pipeline.cc @@ -20,20 +20,24 @@ #include #include "xls/codegen_v_1_5/block_conversion_pass.h" +#include "xls/codegen_v_1_5/block_conversion_wrapper_pass.h" #include "xls/codegen_v_1_5/block_finalization_pass.h" #include "xls/codegen_v_1_5/channel_to_port_io_lowering_pass.h" #include "xls/codegen_v_1_5/flow_control_insertion_pass.h" #include "xls/codegen_v_1_5/function_io_lowering_pass.h" #include "xls/codegen_v_1_5/idle_insertion_pass.h" #include "xls/codegen_v_1_5/pipeline_register_insertion_pass.h" +#include "xls/codegen_v_1_5/register_cleanup_pass.h" #include "xls/codegen_v_1_5/scheduled_block_conversion_pass.h" #include "xls/codegen_v_1_5/scheduling_pass.h" #include "xls/codegen_v_1_5/state_to_register_io_lowering_pass.h" +#include "xls/passes/dce_pass.h" +#include "xls/passes/optimization_pass.h" namespace xls::codegen { -std::unique_ptr -CreateBlockConversionPassPipeline() { +std::unique_ptr CreateBlockConversionPassPipeline( + OptimizationContext& opt_context) { auto top = std::make_unique( "block_conversion", "Top level codegen v1.5 block conversion pipeline"); @@ -64,6 +68,14 @@ CreateBlockConversionPassPipeline() { // Lower scheduled block to standard block, inlining each stage. top->Add(); + // Clean up unused registers & load-enable bits (including flow-control + // registers). + top->Add(); + + // Remove anything we created & then left dead. + top->Add( + std::make_unique(), opt_context); + return top; } diff --git a/xls/codegen_v_1_5/block_conversion_pass_pipeline.h b/xls/codegen_v_1_5/block_conversion_pass_pipeline.h index 7541593a34..37d20dcbeb 100644 --- a/xls/codegen_v_1_5/block_conversion_pass_pipeline.h +++ b/xls/codegen_v_1_5/block_conversion_pass_pipeline.h @@ -18,13 +18,14 @@ #include #include "xls/codegen_v_1_5/block_conversion_pass.h" +#include "xls/passes/optimization_pass.h" namespace xls::codegen { // Returns a pipeline which converts an unscheduled IR package into a standard // block. -std::unique_ptr -CreateBlockConversionPassPipeline(); +std::unique_ptr CreateBlockConversionPassPipeline( + OptimizationContext& opt_context); } // namespace xls::codegen diff --git a/xls/codegen_v_1_5/block_conversion_pass_pipeline_test.cc b/xls/codegen_v_1_5/block_conversion_pass_pipeline_test.cc index 5bff3c171c..20875c5ce5 100644 --- a/xls/codegen_v_1_5/block_conversion_pass_pipeline_test.cc +++ b/xls/codegen_v_1_5/block_conversion_pass_pipeline_test.cc @@ -54,7 +54,6 @@ #include "xls/common/status/matchers.h" #include "xls/common/status/ret_check.h" #include "xls/common/status/status_macros.h" -#include "xls/estimators/delay_model/delay_estimator.h" #include "xls/interpreter/block_evaluator.h" #include "xls/interpreter/block_interpreter.h" #include "xls/ir/bits.h" @@ -97,6 +96,7 @@ using ::testing::ElementsAre; using ::testing::Eq; using ::testing::Ge; using ::testing::HasSubstr; +using ::testing::IsEmpty; using ::testing::Optional; using ::testing::Pair; using ::testing::SizeIs; @@ -282,7 +282,7 @@ TEST_F(BlockConversionTest, SimpleFunction) { m::OutputPort("out", m::Add(m::InputPort("x"), m::InputPort("y")))); } -TEST_F(BlockConversionTest, DISABLED_SimpleFunctionWithNamedOutput) { +TEST_F(BlockConversionTest, SimpleFunctionWithNamedOutput) { auto p = CreatePackage(); FunctionBuilder fb(TestName(), p.get()); BValue x = fb.Param("x", p->GetBitsType(32)); @@ -331,7 +331,7 @@ TEST_F(BlockConversionTest, ZeroWidthInputsAndOutput) { EXPECT_EQ(block->GetPorts().size(), 4); } -TEST_F(BlockConversionTest, DISABLED_SimplePipelinedFunction) { +TEST_F(BlockConversionTest, SimplePipelinedFunction) { auto p = CreatePackage(); FunctionBuilder fb(TestName(), p.get()); BValue x = fb.Param("x", p->GetBitsType(32)); @@ -348,11 +348,13 @@ TEST_F(BlockConversionTest, DISABLED_SimplePipelinedFunction) { SchedulingOptions().pipeline_stages(3))); EXPECT_THAT(GetOutputPort(block), - m::OutputPort(m::Neg(m::Register(m::Not(m::Register( - m::Add(m::InputPort("x"), m::InputPort("y")))))))); + m::OutputPort(m::Neg(m::Register(m::Not( + m::Register(m::Add(m::InputPort("x"), m::InputPort("y")))))))) + << "\n\nIR:\n" + << block->DumpIr(); } -TEST_F(BlockConversionTest, DISABLED_TrivialPipelinedFunctionNoFlopping) { +TEST_F(BlockConversionTest, TrivialPipelinedFunctionNoFlopping) { auto p = CreatePackage(); FunctionBuilder fb(TestName(), p.get()); BValue x = fb.Param("x", p->GetBitsType(32)); @@ -373,7 +375,7 @@ TEST_F(BlockConversionTest, DISABLED_TrivialPipelinedFunctionNoFlopping) { m::Add(m::InputPort("x"), m::InputPort("y")))))))); } -TEST_F(BlockConversionTest, DISABLED_TrivialPipelinedFunctionFloppedInputs) { +TEST_F(BlockConversionTest, TrivialPipelinedFunctionFloppedInputs) { auto p = CreatePackage(); FunctionBuilder fb(TestName(), p.get()); BValue x = fb.Param("x", p->GetBitsType(32)); @@ -395,7 +397,7 @@ TEST_F(BlockConversionTest, DISABLED_TrivialPipelinedFunctionFloppedInputs) { m::Register(m::InputPort("x")), m::Register(m::InputPort("y"))))))))); } -TEST_F(BlockConversionTest, DISABLED_TrivialPipelinedFunctionFloppedOutputs) { +TEST_F(BlockConversionTest, TrivialPipelinedFunctionFloppedOutputs) { auto p = CreatePackage(); FunctionBuilder fb(TestName(), p.get()); BValue x = fb.Param("x", p->GetBitsType(32)); @@ -416,7 +418,7 @@ TEST_F(BlockConversionTest, DISABLED_TrivialPipelinedFunctionFloppedOutputs) { m::Add(m::InputPort("x"), m::InputPort("y"))))))))); } -TEST_F(BlockConversionTest, DISABLED_TrivialPipelinedFunctionFloppedIo) { +TEST_F(BlockConversionTest, TrivialPipelinedFunctionFloppedIo) { auto p = CreatePackage(); FunctionBuilder fb(TestName(), p.get()); BValue x = fb.Param("x", p->GetBitsType(32)); @@ -438,7 +440,7 @@ TEST_F(BlockConversionTest, DISABLED_TrivialPipelinedFunctionFloppedIo) { m::Register(m::InputPort("y")))))))))); } -TEST_F(BlockConversionTest, DISABLED_ZeroWidthPipeline) { +TEST_F(BlockConversionTest, ZeroWidthPipeline) { auto p = CreatePackage(); FunctionBuilder fb(TestName(), p.get()); BValue x = fb.Param("x", p->GetTupleType({})); @@ -454,7 +456,28 @@ TEST_F(BlockConversionTest, DISABLED_ZeroWidthPipeline) { "clk"), SchedulingOptions().pipeline_stages(3))); - EXPECT_EQ(block->GetRegisters().size(), 4); + EXPECT_THAT(block->GetRegisters(), IsEmpty()); +} + +TEST_F(BlockConversionTest, ZeroWidthPipelineWithValidControl) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue x = fb.Param("x", p->GetTupleType({})); + BValue y = fb.Param("y", p->GetBitsType(0)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, + fb.BuildWithReturnValue(fb.Tuple({x, y}))); + + XLS_ASSERT_OK_AND_ASSIGN( + Block * block, + ConvertToBlock(f, + CodegenOptions() + .flop_inputs(false) + .flop_outputs(false) + .clock_name("clk") + .valid_control("inputs_valid", "output_valid"), + SchedulingOptions().pipeline_stages(3))); + + EXPECT_THAT(block->GetRegisters(), SizeIs(2)); } // Verifies that an implicit token, as generated by the DSLX IR converter, is @@ -473,11 +496,10 @@ fn __itok__implicit_token__main(__token: token, __activated: bits[1]) -> fn __implicit_token__main() -> () { after_all.9: token = after_all(id=9) literal.10: bits[1] = literal(value=1, id=10) - invoke.11: (token, ()) = invoke(after_all.9, literal.10, - to_apply=__itok__implicit_token__main, id=11) tuple_index.12: token = - tuple_index(invoke.11, index=0, id=12) invoke.13: (token, ()) = - invoke(tuple_index.12, literal.10, to_apply=__itok__implicit_token__main, - id=13) ret tuple_index.14: () = tuple_index(invoke.13, index=1, id=14) + invoke.11: (token, ()) = invoke(after_all.9, literal.10, to_apply=__itok__implicit_token__main, id=11) + tuple_index.12: token = tuple_index(invoke.11, index=0, id=12) + invoke.13: (token, ()) = invoke(tuple_index.12, literal.10, to_apply=__itok__implicit_token__main, id=13) + ret tuple_index.14: () = tuple_index(invoke.13, index=1, id=14) } )"; XLS_ASSERT_OK_AND_ASSIGN(auto p, Parser::ParsePackage(kIrText)); @@ -6394,7 +6416,7 @@ TEST_F(ProcConversionTestFixture, } } -TEST_F(ProcConversionTestFixture, DISABLED_SimpleFunctionWithProcsPresent) { +TEST_F(ProcConversionTestFixture, SimpleFunctionWithProcsPresent) { XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr p, CreateMultiProcPackage(/*with_functions=*/true)); XLS_ASSERT_OK_AND_ASSIGN(Function * f0, p->GetFunction("f0")); diff --git a/xls/codegen_v_1_5/block_conversion_wrapper_pass.cc b/xls/codegen_v_1_5/block_conversion_wrapper_pass.cc new file mode 100644 index 0000000000..a3ce2b6054 --- /dev/null +++ b/xls/codegen_v_1_5/block_conversion_wrapper_pass.cc @@ -0,0 +1,32 @@ +// Copyright 2021 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen_v_1_5/block_conversion_wrapper_pass.h" + +#include "absl/status/statusor.h" +#include "xls/codegen_v_1_5/block_conversion_pass.h" +#include "xls/ir/package.h" +#include "xls/passes/optimization_pass.h" +#include "xls/passes/pass_base.h" + +namespace xls::codegen { + +absl::StatusOr BlockConversionWrapperPass::RunInternal( + Package* package, const BlockConversionPassOptions& options, + PassResults* results) const { + return wrapped_pass_->Run(package, OptimizationPassOptions(options), results, + opt_context_); +} + +} // namespace xls::codegen diff --git a/xls/codegen_v_1_5/block_conversion_wrapper_pass.h b/xls/codegen_v_1_5/block_conversion_wrapper_pass.h new file mode 100644 index 0000000000..caa9025638 --- /dev/null +++ b/xls/codegen_v_1_5/block_conversion_wrapper_pass.h @@ -0,0 +1,74 @@ +// Copyright 2021 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef XLS_CODEGEN_V_1_5_BLOCK_CONVERSION_WRAPPER_PASS_H_ +#define XLS_CODEGEN_V_1_5_BLOCK_CONVERSION_WRAPPER_PASS_H_ + +#include +#include + +#include "absl/status/statusor.h" +#include "absl/strings/str_format.h" +#include "absl/types/span.h" +#include "xls/codegen_v_1_5/block_conversion_pass.h" +#include "xls/ir/package.h" +#include "xls/passes/optimization_pass.h" +#include "xls/passes/pass_base.h" + +namespace xls::codegen { + +// A codegen pass wrapper which wraps a OptimizationFunctionBasePass. This is +// useful for adding an optimization or transformation pass (most passes in +// xls/passes are OptimizationFunctionBasePasses) to the codegen pipeline. The +// wrapped pass is run on the block being lowered to Verilog. +// +// Takes the OptimizationContext object at construction, since it's specific to +// optimization passes & cannot be passed via a codegen pass's Run function. +class BlockConversionWrapperPass : public BlockConversionPass { + public: + explicit BlockConversionWrapperPass( + std::unique_ptr wrapped_pass, + OptimizationContext& opt_context) + : BlockConversionPass( + absl::StrFormat("codegen_%s", wrapped_pass->short_name()), + absl::StrFormat("%s (codegen)", wrapped_pass->long_name())), + wrapped_pass_(std::move(wrapped_pass)), + opt_context_(opt_context) {} + ~BlockConversionWrapperPass() override = default; + + bool IsCompound() const override { return wrapped_pass_->IsCompound(); } + + absl::StatusOr RunInternal(Package* package, + const BlockConversionPassOptions& options, + PassResults* results) const final; + + absl::StatusOr RunNested(Package* package, + const BlockConversionPassOptions& options, + PassResults* results, + PassInvocation& invocation, + absl::Span + invariant_checkers) const override { + return wrapped_pass_->RunNested(package, OptimizationPassOptions(options), + results, opt_context_, invocation, + /*invariant_checkers=*/{}); + } + + private: + std::unique_ptr wrapped_pass_; + OptimizationContext& opt_context_; +}; + +} // namespace xls::codegen + +#endif // XLS_CODEGEN_V_1_5_BLOCK_CONVERSION_WRAPPER_PASS_H_ diff --git a/xls/codegen_v_1_5/block_conversion_wrapper_pass_test.cc b/xls/codegen_v_1_5/block_conversion_wrapper_pass_test.cc new file mode 100644 index 0000000000..45600011a9 --- /dev/null +++ b/xls/codegen_v_1_5/block_conversion_wrapper_pass_test.cc @@ -0,0 +1,76 @@ +// Copyright 2021 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen_v_1_5/block_conversion_wrapper_pass.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/status/statusor.h" +#include "xls/codegen_v_1_5/block_conversion_pass.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/block.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_matcher.h" +#include "xls/ir/ir_test_base.h" +#include "xls/passes/dce_pass.h" +#include "xls/passes/optimization_pass.h" +#include "xls/passes/pass_base.h" + +namespace m = ::xls::op_matchers; + +namespace xls::codegen { +namespace { + +using ::absl_testing::IsOkAndHolds; + +class BlockConversionWrapperPassTest : public IrTestBase { + protected: + absl::StatusOr Run(Block* block) { + PassResults results; + OptimizationContext optimization_context; + return BlockConversionWrapperPass( + std::make_unique(), + optimization_context) + .Run(block->package(), BlockConversionPassOptions(), &results); + } +}; + +TEST_F(BlockConversionWrapperPassTest, WrappedDce) { + auto p = CreatePackage(); + + BlockBuilder bb(TestName(), p.get()); + XLS_ASSERT_OK(bb.block()->AddClockPort("clk")); + BValue a = bb.InputPort("a", p->GetBitsType(32)); + BValue b = bb.InputPort("b", p->GetBitsType(32)); + bb.Add(a, b); + bb.OutputPort("c", bb.Subtract(a, b)); + XLS_ASSERT_OK_AND_ASSIGN(Block * block, bb.Build()); + + // The add should be eliminated. + EXPECT_EQ(block->node_count(), 5); + EXPECT_THAT(Run(block), IsOkAndHolds(true)); + EXPECT_EQ(block->node_count(), 4); + + EXPECT_THAT(FindNode("c", block), + m::OutputPort(m::Sub(m::InputPort("a"), m::InputPort("b")))); + + // Pass should be idempotent. + EXPECT_THAT(Run(block), IsOkAndHolds(false)); + EXPECT_EQ(block->node_count(), 4); +} + +} // namespace +} // namespace xls::codegen diff --git a/xls/codegen_v_1_5/convert_to_block.cc b/xls/codegen_v_1_5/convert_to_block.cc index f59676ebcf..c5546b228c 100644 --- a/xls/codegen_v_1_5/convert_to_block.cc +++ b/xls/codegen_v_1_5/convert_to_block.cc @@ -26,6 +26,7 @@ #include "xls/common/status/status_macros.h" #include "xls/estimators/delay_model/delay_estimator.h" #include "xls/ir/package.h" +#include "xls/passes/optimization_pass.h" #include "xls/passes/pass_base.h" #include "xls/scheduling/scheduling_options.h" #include "xls/scheduling/scheduling_result.h" @@ -56,8 +57,9 @@ absl::Status ConvertToBlock( .codegen_options = std::move(codegen_options), .package_schedule = schedule}; + OptimizationContext opt_context; std::unique_ptr pipeline = - CreateBlockConversionPassPipeline(); + CreateBlockConversionPassPipeline(opt_context); PassResults results; XLS_ASSIGN_OR_RETURN(bool result, pipeline->Run(p, options, &results)); XLS_RET_CHECK(result); diff --git a/xls/codegen_v_1_5/flow_control_insertion_pass.cc b/xls/codegen_v_1_5/flow_control_insertion_pass.cc index 54712f11b7..48b5d80686 100644 --- a/xls/codegen_v_1_5/flow_control_insertion_pass.cc +++ b/xls/codegen_v_1_5/flow_control_insertion_pass.cc @@ -66,6 +66,34 @@ absl::StatusOr FlowControlInsertionPass::InsertFlowControl( return changed; } + const bool uses_valid = + options.codegen_options.valid_control().has_value() && + (options.codegen_options.valid_control()->has_input_name() || + options.codegen_options.valid_control()->has_output_name()); + if (block->source() != nullptr && block->source()->IsFunction() && + !uses_valid) { + // If this block represents a function and neither gates on valid nor + // signals valid, then the pipeline registers can just be considered + // always-valid and always-ready. + for (Stage& stage : block->stages()) { + if (!IsLiteralUnsignedOne(stage.inputs_valid())) { + XLS_RETURN_IF_ERROR( + stage.inputs_valid() + ->ReplaceUsesWithNew(Value(UBits(1, 1))) + .status()); + changed = true; + } + if (!IsLiteralUnsignedOne(stage.outputs_ready())) { + XLS_RETURN_IF_ERROR( + stage.outputs_ready() + ->ReplaceUsesWithNew(Value(UBits(1, 1))) + .status()); + changed = true; + } + } + return changed; + } + absl::FixedArray stage_done(block->stages().size()); for (int64_t stage_index = 0; stage_index < block->stages().size(); ++stage_index) { diff --git a/xls/codegen_v_1_5/function_io_lowering_pass.cc b/xls/codegen_v_1_5/function_io_lowering_pass.cc index 997622d6a3..3e963095c5 100644 --- a/xls/codegen_v_1_5/function_io_lowering_pass.cc +++ b/xls/codegen_v_1_5/function_io_lowering_pass.cc @@ -182,10 +182,13 @@ absl::StatusOr FunctionIOLoweringPass::LowerReturnValue( return absl::UnimplementedError("Splitting outputs not supported."); } - // The return value is always expected to be in the last stage. - XLS_ASSIGN_OR_RETURN(int64_t stage_index, - block->GetStageIndex(block->source_return_value())); - XLS_RET_CHECK_EQ(stage_index, block->stages().size() - 1); + // The return value is always assumed to be in the last stage - or possibly + // unscheduled, if (e.g.) returning a literal. + if (block->IsStaged(block->source_return_value())) { + XLS_ASSIGN_OR_RETURN(int64_t stage_index, + block->GetStageIndex(block->source_return_value())); + XLS_RET_CHECK_EQ(stage_index, block->stages().size() - 1); + } std::optional output_valid = std::nullopt; if (options.codegen_options.valid_control().has_value() && diff --git a/xls/codegen_v_1_5/function_io_lowering_pass_test.cc b/xls/codegen_v_1_5/function_io_lowering_pass_test.cc index 216d5e3f5f..8f44a4d6bd 100644 --- a/xls/codegen_v_1_5/function_io_lowering_pass_test.cc +++ b/xls/codegen_v_1_5/function_io_lowering_pass_test.cc @@ -108,12 +108,11 @@ class FunctionIOLoweringPassTest : public IrTestBase { // Runs PipelineRegisterInsertionPass and BlockFinalizationPass, then // verify & return the output of block interpretation. - absl::StatusOr RunFunctionalTest(Package* p, Block* block, - absl::Span inputs, - int64_t expected_latency, - FunctionalTestOptions options = {}) { + absl::StatusOr RunFunctionalTest( + Package* p, Block* block, const BlockConversionPassOptions& pass_options, + absl::Span inputs, int64_t expected_latency, + FunctionalTestOptions options = {}) { std::string block_name = block->name(); - BlockConversionPassOptions pass_options; PassResults pass_results; XLS_RETURN_IF_ERROR(PipelineRegisterInsertionPass() .Run(p, pass_options, &pass_results) @@ -221,7 +220,7 @@ TEST_F(FunctionIOLoweringPassTest, SingleStage) { ElementsAre(Property(&PortNode::GetName, "out"))); EXPECT_EQ(sb->stages().size(), 1); - EXPECT_THAT(RunFunctionalTest(p.get(), sb, inputs, + EXPECT_THAT(RunFunctionalTest(p.get(), sb, options, inputs, /*expected_latency=*/1), IsOkAndHolds(expected_output)); } @@ -264,7 +263,7 @@ TEST_F(FunctionIOLoweringPassTest, SingleStageWithInputValid) { EXPECT_EQ(sb->stages().size(), 1); EXPECT_THAT( - RunFunctionalTest(p.get(), sb, inputs, + RunFunctionalTest(p.get(), sb, options, inputs, /*expected_latency=*/1, {.input_valid = "x_valid"}), IsOkAndHolds(expected_output)); } @@ -308,7 +307,7 @@ TEST_F(FunctionIOLoweringPassTest, SingleStageWithIOValid) { EXPECT_EQ(sb->stages().size(), 1); EXPECT_THAT(RunFunctionalTest( - p.get(), sb, inputs, + p.get(), sb, options, inputs, /*expected_latency=*/1, {.input_valid = "x_valid", .output_valid = "out_valid"}), IsOkAndHolds(expected_output)); @@ -350,7 +349,7 @@ TEST_F(FunctionIOLoweringPassTest, MultiStage) { ElementsAre(Property(&PortNode::GetName, "out"))); EXPECT_EQ(sb->stages().size(), 3); - EXPECT_THAT(RunFunctionalTest(p.get(), sb, inputs, + EXPECT_THAT(RunFunctionalTest(p.get(), sb, options, inputs, /*expected_latency=*/3), IsOkAndHolds(expected_output)); } @@ -396,7 +395,7 @@ TEST_F(FunctionIOLoweringPassTest, MultiStageWithInputValid) { EXPECT_EQ(sb->stages().size(), 3); EXPECT_THAT( - RunFunctionalTest(p.get(), sb, inputs, + RunFunctionalTest(p.get(), sb, options, inputs, /*expected_latency=*/3, {.input_valid = "x_valid"}), IsOkAndHolds(expected_output)); } @@ -442,7 +441,7 @@ TEST_F(FunctionIOLoweringPassTest, MultiStageWithIOValidDelayed) { Property(&PortNode::GetName, "out"))); EXPECT_EQ(sb->stages().size(), 3); - EXPECT_THAT(RunFunctionalTest(p.get(), sb, inputs, + EXPECT_THAT(RunFunctionalTest(p.get(), sb, options, inputs, /*expected_latency=*/3, {.input_valid = "x_valid", .output_valid = "out_valid", @@ -492,7 +491,7 @@ TEST_F(FunctionIOLoweringPassTest, MultiStageWithIOValid) { EXPECT_EQ(sb->stages().size(), 3); EXPECT_THAT(RunFunctionalTest( - p.get(), sb, inputs, + p.get(), sb, options, inputs, /*expected_latency=*/3, {.input_valid = "x_valid", .output_valid = "out_valid"}), IsOkAndHolds(expected_output)); @@ -541,7 +540,7 @@ TEST_F(FunctionIOLoweringPassTest, MultiStageMultiInputWithIOValid) { EXPECT_EQ(sb->stages().size(), 3); EXPECT_THAT(RunFunctionalTest( - p.get(), sb, inputs, + p.get(), sb, options, inputs, /*expected_latency=*/3, {.input_valid = "x_valid", .output_valid = "out_valid"}), IsOkAndHolds(expected_output)); @@ -587,7 +586,7 @@ TEST_F(FunctionIOLoweringPassTest, MultiStageMultiInputWithFloppedInputs) { ElementsAre(Property(&PortNode::GetName, "out"))); EXPECT_EQ(sb->stages().size(), 3); - EXPECT_THAT(RunFunctionalTest(p.get(), sb, inputs, + EXPECT_THAT(RunFunctionalTest(p.get(), sb, options, inputs, /*expected_latency=*/4), IsOkAndHolds(expected_output)); } @@ -632,7 +631,7 @@ TEST_F(FunctionIOLoweringPassTest, MultiStageMultiInputWithFloppedOutputs) { ElementsAre(Property(&PortNode::GetName, "out"))); EXPECT_EQ(sb->stages().size(), 3); - EXPECT_THAT(RunFunctionalTest(p.get(), sb, inputs, + EXPECT_THAT(RunFunctionalTest(p.get(), sb, options, inputs, /*expected_latency=*/4), IsOkAndHolds(expected_output)); } @@ -678,7 +677,7 @@ TEST_F(FunctionIOLoweringPassTest, MultiStageMultiInputWithFloppedIO) { ElementsAre(Property(&PortNode::GetName, "out"))); EXPECT_EQ(sb->stages().size(), 3); - EXPECT_THAT(RunFunctionalTest(p.get(), sb, inputs, + EXPECT_THAT(RunFunctionalTest(p.get(), sb, options, inputs, /*expected_latency=*/5), IsOkAndHolds(expected_output)); } diff --git a/xls/codegen_v_1_5/register_cleanup_pass.cc b/xls/codegen_v_1_5/register_cleanup_pass.cc new file mode 100644 index 0000000000..af97b37df5 --- /dev/null +++ b/xls/codegen_v_1_5/register_cleanup_pass.cc @@ -0,0 +1,188 @@ +// Copyright 2025 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen_v_1_5/register_cleanup_pass.h" + +#include +#include +#include + +#include "absl/algorithm/container.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/status/statusor.h" +#include "absl/types/span.h" +#include "xls/codegen_v_1_5/block_conversion_pass.h" +#include "xls/common/status/status_macros.h" +#include "xls/data_structures/transitive_closure.h" +#include "xls/ir/block.h" +#include "xls/ir/node.h" +#include "xls/ir/nodes.h" +#include "xls/ir/package.h" +#include "xls/ir/register.h" +#include "xls/ir/value.h" +#include "xls/ir/value_utils.h" +#include "xls/passes/node_dependency_analysis.h" +#include "xls/passes/partial_info_query_engine.h" +#include "xls/passes/pass_base.h" +#include "xls/passes/query_engine.h" + +namespace xls::codegen { + +namespace { + +absl::StatusOr> GetRegisterWrites(Block* block, + Register* reg) { + XLS_ASSIGN_OR_RETURN(absl::Span writes, + block->GetRegisterWrites(reg)); + return std::vector(writes.begin(), writes.end()); +} + +} // namespace + +absl::StatusOr RegisterCleanupPass::RemoveTrivialLoadEnables( + Block* block, QueryEngine& query_engine) const { + bool changed = false; + for (Register* reg : block->GetRegisters()) { + XLS_ASSIGN_OR_RETURN(std::vector writes, + GetRegisterWrites(block, reg)); + for (RegisterWrite* write : writes) { + if (write->load_enable().has_value() && + query_engine.IsAllOnes(*write->load_enable())) { + XLS_RETURN_IF_ERROR( + write + ->ReplaceUsesWithNew(write->data(), std::nullopt, + write->reset(), reg) + .status()); + XLS_RETURN_IF_ERROR(block->RemoveNode(write)); + changed = true; + } + } + } + return changed; +} + +absl::StatusOr RegisterCleanupPass::RemoveImpossibleWrites( + Block* block, QueryEngine& query_engine) const { + for (Register* reg : block->GetRegisters()) { + XLS_ASSIGN_OR_RETURN(std::vector writes, + GetRegisterWrites(block, reg)); + for (RegisterWrite* write : writes) { + bool write_disabled = write->load_enable().has_value() && + query_engine.IsAllZeros(*write->load_enable()); + if (write_disabled) { + XLS_RETURN_IF_ERROR( + write->ReplaceUsesWithNew(Value::Tuple({})).status()); + XLS_RETURN_IF_ERROR(block->RemoveNode(write)); + } + } + + XLS_ASSIGN_OR_RETURN(writes, GetRegisterWrites(block, reg)); + if (writes.empty() || (reg->reset_value().has_value() && + absl::c_all_of(writes, [&](RegisterWrite* write) { + return query_engine.KnownValue(write->data()) == + *reg->reset_value(); + }))) { + // Nothing can ever write a new value to this register, so it's equivalent + // to its reset value. + XLS_ASSIGN_OR_RETURN(RegisterRead * read, block->GetRegisterRead(reg)); + XLS_RETURN_IF_ERROR( + read->ReplaceUsesWithNew( + reg->reset_value().value_or(ZeroOfType(reg->type()))) + .status()); + XLS_RETURN_IF_ERROR(block->RemoveNode(read)); + XLS_RETURN_IF_ERROR(block->RemoveRegister(reg)); + } + } + return false; +} + +absl::StatusOr RegisterCleanupPass::RemoveUnreadRegisters( + Block* block, QueryEngine& query_engine) const { + absl::flat_hash_map> + can_receive_value_from; + absl::flat_hash_set directly_read_registers; + NodeBackwardDependencyAnalysis nda; + XLS_RETURN_IF_ERROR(nda.Attach(block).status()); + for (Register* reg : block->GetRegisters()) { + XLS_ASSIGN_OR_RETURN(RegisterRead * read, block->GetRegisterRead(reg)); + for (Node* user : nda.NodesDependingOn(read)) { + if (user->Is()) { + can_receive_value_from[reg].insert( + user->As()->GetRegister()); + } else if (user->Is()) { + directly_read_registers.insert(reg); + } + } + } + + absl::flat_hash_map> + transitive_receivers = TransitiveClosure(can_receive_value_from); + std::vector unread_registers; + for (Register* reg : block->GetRegisters()) { + if (!directly_read_registers.contains(reg) && + absl::c_none_of(transitive_receivers[reg], [&](Register* user) { + return directly_read_registers.contains(user); + })) { + unread_registers.push_back(reg); + } + } + + for (Register* reg : unread_registers) { + XLS_ASSIGN_OR_RETURN(RegisterRead * read, block->GetRegisterRead(reg)); + XLS_ASSIGN_OR_RETURN(absl::Span writes, + block->GetRegisterWrites(reg)); + if (!read->IsDead()) { + XLS_RETURN_IF_ERROR( + read->ReplaceUsesWithNew( + reg->reset_value().value_or(ZeroOfType(reg->type()))) + .status()); + } + XLS_RETURN_IF_ERROR(block->RemoveNode(read)); + for (RegisterWrite* write : writes) { + if (!write->IsDead()) { + XLS_RETURN_IF_ERROR( + write->ReplaceUsesWithNew(Value::Tuple({})).status()); + } + XLS_RETURN_IF_ERROR(block->RemoveNode(write)); + } + XLS_RETURN_IF_ERROR(block->RemoveRegister(reg)); + } + return !unread_registers.empty(); +} + +absl::StatusOr RegisterCleanupPass::RunInternal( + Package* package, const BlockConversionPassOptions& options, + PassResults* results) const { + bool changed = false; + for (const std::unique_ptr& block : package->blocks()) { + PartialInfoQueryEngine query_engine; + XLS_RETURN_IF_ERROR(query_engine.Populate(block.get()).status()); + + XLS_ASSIGN_OR_RETURN(bool changed_load_enables, + RemoveTrivialLoadEnables(block.get(), query_engine)); + changed |= changed_load_enables; + + XLS_ASSIGN_OR_RETURN(bool changed_writes, + RemoveImpossibleWrites(block.get(), query_engine)); + changed |= changed_writes; + + XLS_ASSIGN_OR_RETURN(bool changed_registers, + RemoveUnreadRegisters(block.get(), query_engine)); + changed |= changed_registers; + } + return changed; +} + +} // namespace xls::codegen diff --git a/xls/codegen_v_1_5/register_cleanup_pass.h b/xls/codegen_v_1_5/register_cleanup_pass.h new file mode 100644 index 0000000000..a2a827d8ec --- /dev/null +++ b/xls/codegen_v_1_5/register_cleanup_pass.h @@ -0,0 +1,61 @@ +// Copyright 2025 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef XLS_CODEGEN_V_1_5_REGISTER_CLEANUP_PASS_H_ +#define XLS_CODEGEN_V_1_5_REGISTER_CLEANUP_PASS_H_ + +#include "absl/status/statusor.h" +#include "xls/codegen_v_1_5/block_conversion_pass.h" +#include "xls/ir/package.h" +#include "xls/passes/pass_base.h" +#include "xls/passes/query_engine.h" + +namespace xls::codegen { + +// A pass which cleans up registers and register writes in the block. +// +// Specific cleanup actions performed: +// 1. Removes load-enable signals from register writes if the load-enable is +// statically determined to be always one (true). +// 2. Removes register writes if the load-enable is statically determined to +// be always zero (false). +// 3. Replaces registers which are never written to, or only ever have values +// identical to their reset value written to them, with literals. +// 4. Removes registers which do not transitively drive any output port. +// +// This pass is intended to run late in the codegen pipeline after flow control +// and pipeline registers have been inserted. +class RegisterCleanupPass : public BlockConversionPass { + public: + RegisterCleanupPass() + : BlockConversionPass("register_cleanup", + "Remove dead registers & unused load-enable bits") { + } + + protected: + absl::StatusOr RemoveTrivialLoadEnables( + Block* block, QueryEngine& query_engine) const; + absl::StatusOr RemoveImpossibleWrites(Block* block, + QueryEngine& query_engine) const; + absl::StatusOr RemoveUnreadRegisters(Block* block, + QueryEngine& query_engine) const; + + absl::StatusOr RunInternal(Package* package, + const BlockConversionPassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls::codegen + +#endif // XLS_CODEGEN_V_1_5_REGISTER_CLEANUP_PASS_H_