diff --git a/xls/dslx/interp_value_utils.cc b/xls/dslx/interp_value_utils.cc index 489faacee9..ac1024a7df 100644 --- a/xls/dslx/interp_value_utils.cc +++ b/xls/dslx/interp_value_utils.cc @@ -1005,7 +1005,9 @@ absl::StatusOr> CreateChannelReferencePair( lhs_elements.push_back(lhs_rhs.first); rhs_elements.push_back(lhs_rhs.second); } - int64_t array_id = (*channel_instance_allocator)(); + int64_t array_id = channel_instance_allocator.has_value() + ? (*channel_instance_allocator)() + : 0; return std::make_pair( InterpValue::MakeChannelArray(ChannelDirection::kOut, array_id, definer.has_value() ? *definer : nullptr, diff --git a/xls/dslx/interp_value_utils_test.cc b/xls/dslx/interp_value_utils_test.cc index 74bd3c3691..ee4be07b19 100644 --- a/xls/dslx/interp_value_utils_test.cc +++ b/xls/dslx/interp_value_utils_test.cc @@ -1241,6 +1241,29 @@ TEST(InterpValueHelpersTest, GetLeafChannelReferences) { testing::ElementsAre(ch0, ch1, ch2, ch3)); } +TEST(InterpValueHelpersTest, + CreateChannelReferencePairWithoutAllocatorOnArrayType) { + auto chan_type = std::make_unique(BitsType::MakeU32(), + ChannelDirection::kOut); + auto inner_array_type = + std::make_unique(std::move(chan_type), TypeDim::CreateU32(3)); + ArrayType outer_array_type(std::move(inner_array_type), + TypeDim::CreateU32(2)); + + XLS_ASSERT_OK_AND_ASSIGN( + auto pair, + CreateChannelReferencePair(&outer_array_type, + /*channel_instance_allocator=*/std::nullopt)); + EXPECT_TRUE(pair.first.IsChannelArray()); + EXPECT_TRUE(pair.second.IsChannelArray()); + EXPECT_EQ(pair.first.GetChannelArrayOrDie().direction(), + ChannelDirection::kOut); + EXPECT_EQ(pair.second.GetChannelArrayOrDie().direction(), + ChannelDirection::kIn); + EXPECT_EQ(GetLeafChannelReferences(pair.first).size(), 6); + EXPECT_EQ(GetLeafChannelReferences(pair.second).size(), 6); +} + TEST(InterpValueHelpersTest, ValueToInterpValueSumRejectsInvalidTag) { FileTable file_table; Module module("test", /*fs_path=*/std::nullopt, file_table); diff --git a/xls/dslx/ir_convert/function_converter.cc b/xls/dslx/ir_convert/function_converter.cc index 82d67e779a..885455bc58 100644 --- a/xls/dslx/ir_convert/function_converter.cc +++ b/xls/dslx/ir_convert/function_converter.cc @@ -3920,6 +3920,32 @@ absl::Status FunctionConverter::DefineProcDefChannelOrArrayIfLocal( /*name_def=*/std::nullopt, value, &direction_type, channel_decls); } +namespace { + +// Returns all leaf `ChannelReference` values from a `ChannelReference` or +// `ChannelArray` in the same index order produced by +// `ChannelScope::CreateAllArrayElementSuffixes()`. +std::vector GetChannelArrayLeafReferencesInScopeOrder( + const InterpValue& channel_or_array) { + std::vector current = {channel_or_array}; + while (!current.empty() && current.front().IsChannelArray()) { + size_t dim_size = current.front().GetChannelArrayOrDie().elements().size(); + std::vector next; + next.reserve(dim_size * current.size()); + for (size_t element_index = 0; element_index < dim_size; ++element_index) { + for (const InterpValue& item : current) { + const auto& elements = item.GetChannelArrayOrDie().elements(); + CHECK_LT(element_index, elements.size()); + next.push_back(elements[element_index]); + } + } + current = std::move(next); + } + return current; +} + +} // namespace + void FunctionConverter::PopulateChannelOrArrayIdToObject( const InterpValue& value, const ChannelOrArray& channel_or_array) { channel_or_array_id_to_object_[GetChannelOrArrayId(value)] = channel_or_array; @@ -3928,7 +3954,8 @@ void FunctionConverter::PopulateChannelOrArrayIdToObject( } ChannelArray* array = std::get(channel_or_array); - std::vector leaves = GetLeafChannelReferences(value); + std::vector leaves = + GetChannelArrayLeafReferencesInScopeOrder(value); if (leaves.size() == array->channels().size()) { for (int i = 0; i < leaves.size(); ++i) { channel_or_array_id_to_object_ @@ -4031,13 +4058,12 @@ absl::Status FunctionConverter::InitProcDefChannels( const ProcDef* proc_def, const InterpValue::ProcInitializer& canonical_initializer, const TypeInfo* constructor_ti) { - ParametricEnv env; proc_def_channel_scope_ = std::make_unique( package_data_.conversion_info, import_data_, options_, absl::down_cast(function_builder_.get()), [this](const Attr* attr) { return GetChannelArrayForAttr(attr); }); proc_def_channel_scope_->EnterFunctionContext(current_type_info_, - std::move(env)); + GetParametricEnv()); channel_scope_ = proc_def_channel_scope_.get(); XLS_ASSIGN_OR_RETURN(const Type* type, @@ -4143,7 +4169,7 @@ absl::Status FunctionConverter::ExpandProcDefChannelReference( std::vector& out) { if (channel_or_array_value.IsChannelArray()) { for (const InterpValue& elem : - channel_or_array_value.GetChannelArrayOrDie().elements()) { + GetChannelArrayLeafReferencesInScopeOrder(channel_or_array_value)) { XLS_RETURN_IF_ERROR(ExpandProcDefChannelReference(elem, out)); } return absl::OkStatus(); @@ -4318,6 +4344,7 @@ absl::Status FunctionConverter::ConvertProcDef( SetNodeToIr((*next_fn)->params()[0]->name_def(), instance.get()); ScopedTypeInfoSwap stis_next(this, next_ti); + proc_def_channel_scope_->EnterFunctionContext(next_ti, env); XLS_RETURN_IF_ERROR(Visit((*next_fn)->body())); XLS_ASSIGN_OR_RETURN( diff --git a/xls/dslx/ir_convert/ir_converter_test.cc b/xls/dslx/ir_convert/ir_converter_test.cc index aca0c5fab0..fa65a33ad1 100644 --- a/xls/dslx/ir_convert/ir_converter_test.cc +++ b/xls/dslx/ir_convert/ir_converter_test.cc @@ -8744,5 +8744,81 @@ fn sum_enumerated(values: u8[3]) -> u32 { ConvertOneFunctionForTest(kProgram, "sum_enumerated")); ExpectIr(converted); } + +TEST_F(IrConverterTest, ProcDefWith2DChannelArrayAndParametricBounds) { + constexpr std::string_view kModule = R"( +#![feature(explicit_state_access)] +#![feature(generics)] + +proc Node { + from_west: chan in, + to_east: chan out, +} + +impl Node { + fn new(from_west: chan in, to_east: chan out) -> Self { + Self { from_west, to_east } + } + fn next(self) { + let (tok, val) = recv(join(), self.from_west); + send(tok, self.to_east, val); + } +} + +proc Grid { + from_wests: chan[COLS + 1][ROWS] in, + to_easts: chan[COLS + 1][ROWS] out, +} + +impl Grid { + fn new() -> Self { + let (to_easts, from_wests) = chan[COLS + 1][ROWS]("east_west"); + unroll_for! (row, _): (u32, ()) in u32:0..ROWS { + unroll_for! (col, _): (u32, ()) in u32:0..COLS { + Node::new(from_wests[row][col], to_easts[row][col + 1]).spawn(); + }(()); + }(()); + Self { from_wests, to_easts } + } + fn next(self) { + const FIRST_COL: u32 = 0; + const LAST_COL = COLS; + unroll_for! (row, _): (u32, ()) in u32:0..ROWS { + send(join(), self.to_easts[row][FIRST_COL], row); + recv(join(), self.from_wests[row][LAST_COL]); + }(()); + } +} + +proc Top {} +impl Top { + fn new() -> Self { + Grid<2, 2>::new().spawn(); + Self {} + } + fn next(self) {} +} +)"; + + ConvertOptions options = kProcScopedChannelOptions; + XLS_ASSERT_OK_AND_ASSIGN(std::string converted, + ConvertModuleForTest(kModule, options)); + EXPECT_THAT(converted, + HasSubstr("proc_instantiation __test_module__Node__0_0_next_inst(" + "_east_west__0_0, _east_west__0_1, " + "proc=__test_module__Node__0_0_next)")); + EXPECT_THAT(converted, + HasSubstr("proc_instantiation __test_module__Node__1_0_next_inst(" + "_east_west__0_1, _east_west__0_2, " + "proc=__test_module__Node__1_0_next)")); + EXPECT_THAT(converted, + HasSubstr("proc_instantiation __test_module__Node__0_1_next_inst(" + "_east_west__1_0, _east_west__1_1, " + "proc=__test_module__Node__0_1_next)")); + EXPECT_THAT(converted, + HasSubstr("proc_instantiation __test_module__Node__1_1_next_inst(" + "_east_west__1_1, _east_west__1_2, " + "proc=__test_module__Node__1_1_next)")); +} } // namespace } // namespace xls::dslx diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ProcDefTaking2DChannelArrayAndDealingOutSubArrays.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ProcDefTaking2DChannelArrayAndDealingOutSubArrays.ir index f2d15d8445..fd4268e6b0 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ProcDefTaking2DChannelArrayAndDealingOutSubArrays.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ProcDefTaking2DChannelArrayAndDealingOutSubArrays.ir @@ -30,8 +30,8 @@ proc __test_module__MiddleProc_next<_ins__0_0: bits[32] in, _ins__1_0: bits[32] chan_interface _ins__1_0(direction=receive, kind=streaming, strictness=proven_mutually_exclusive, flow_control=ready_valid, flop_kind=none) chan_interface _ins__0_1(direction=receive, kind=streaming, strictness=proven_mutually_exclusive, flow_control=ready_valid, flop_kind=none) chan_interface _ins__1_1(direction=receive, kind=streaming, strictness=proven_mutually_exclusive, flow_control=ready_valid, flop_kind=none) - proc_instantiation __test_module__InnerProc_next_inst(_ins__0_0, _ins__1_0, proc=__test_module__InnerProc_next) - proc_instantiation __test_module__InnerProc_next_inst_1(_ins__0_1, _ins__1_1, proc=__test_module__InnerProc_next) + proc_instantiation __test_module__InnerProc_next_inst(_ins__0_0, _ins__0_1, proc=__test_module__InnerProc_next) + proc_instantiation __test_module__InnerProc_next_inst_1(_ins__1_0, _ins__1_1, proc=__test_module__InnerProc_next) __token: token = literal(value=token, id=13) literal.14: bits[1] = literal(value=1, id=14) tuple.15: () = tuple(id=15) diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_ProcDefWith2DChannelArrayDealingOutSubArrays.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_ProcDefWith2DChannelArrayDealingOutSubArrays.ir index 6938862a18..4a8cd7ddf7 100644 --- a/xls/dslx/ir_convert/testdata/ir_converter_test_ProcDefWith2DChannelArrayDealingOutSubArrays.ir +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_ProcDefWith2DChannelArrayDealingOutSubArrays.ir @@ -104,8 +104,8 @@ proc __test_module__TopProc_next<>() { chan _reply__2__1_1(bits[32], id=23, kind=streaming, ops=send_receive, flow_control=ready_valid, strictness=proven_mutually_exclusive) chan_interface _reply__2__1_1(direction=send, kind=streaming, strictness=proven_mutually_exclusive, flow_control=ready_valid, flop_kind=none) chan_interface _reply__2__1_1(direction=receive, kind=streaming, strictness=proven_mutually_exclusive, flow_control=ready_valid, flop_kind=none) - proc_instantiation __test_module__SomeProc_next_inst(_ins_outs__1__0_0, _ins_outs__1__1_0, _reply__1__0_0, _reply__1__1_0, proc=__test_module__SomeProc_next) - proc_instantiation __test_module__SomeProc_next_inst_1(_ins_outs__2__0_1, _ins_outs__2__1_1, _reply__2__0_1, _reply__2__1_1, proc=__test_module__SomeProc_next) + proc_instantiation __test_module__SomeProc_next_inst(_ins_outs__1__0_0, _ins_outs__1__0_1, _reply__1__0_0, _reply__1__0_1, proc=__test_module__SomeProc_next) + proc_instantiation __test_module__SomeProc_next_inst_1(_ins_outs__2__1_0, _ins_outs__2__1_1, _reply__2__1_0, _reply__2__1_1, proc=__test_module__SomeProc_next) i: bits[32] = literal(value=0, id=25) literal.28: bits[32] = literal(value=2, id=28) literal.48: bits[32] = literal(value=2, id=48) diff --git a/xls/dslx/translators/dslx_to_verilog_main.cc b/xls/dslx/translators/dslx_to_verilog_main.cc index 90181aa257..87f79576f5 100644 --- a/xls/dslx/translators/dslx_to_verilog_main.cc +++ b/xls/dslx/translators/dslx_to_verilog_main.cc @@ -141,10 +141,11 @@ absl::Status RealMain(absl::Span paths) { ParseAndTypecheck(text, path, module_name, &import_data, {})); for (const auto& def : tm.module->GetTypeDefinitions()) { - // Ignore private type definitions. + // Ignore private type definitions and proc definitions. XLS_ASSIGN_OR_RETURN(const TypeInfo::TypeSource type_definition_source, tm.type_info->ResolveTypeDefinition(def)); - if (!TypeDefinitionSourceIsPublic(type_definition_source)) { + if (!TypeDefinitionSourceIsPublic(type_definition_source) || + std::holds_alternative(type_definition_source.definition)) { continue; } AstNode* def_node = TypeDefinitionToAstNode(def); diff --git a/xls/dslx/type_system_v2/typecheck_module_v2_proc_test.cc b/xls/dslx/type_system_v2/typecheck_module_v2_proc_test.cc index 2c1d605c3e..2cb358d913 100644 --- a/xls/dslx/type_system_v2/typecheck_module_v2_proc_test.cc +++ b/xls/dslx/type_system_v2/typecheck_module_v2_proc_test.cc @@ -1628,5 +1628,92 @@ impl Top { XLS_EXPECT_OK(TypecheckV2(kProgram, "main", &import_data)); } +TEST(TypecheckV2ProcTest, ProcDefWithArrayTypeAliasState) { + EXPECT_THAT(R"( +#![feature(explicit_state_access)] + +pub const MEM_SIZE = u32:256; +pub type MemWord = u32; +type State = MemWord[MEM_SIZE]; + +pub proc MemoryProc { + addr_in: chan in, + val_out: chan out, + state: State, +} + +impl MemoryProc { + pub fn new(addr_in: chan in, val_out: chan out) -> Self { + Self { addr_in, val_out, state: zero!() } + } + + pub fn next(self) { + let state = read(self.state); + let (tok, addr) = recv(join(), self.addr_in); + let word = state[addr]; + send(tok, self.val_out, word); + let next_state = update(state, addr, word + u32:1); + write(self.state, next_state); + } +} +)", + TypecheckSucceeds(::testing::_)); +} + +TEST(TypecheckV2ProcTest, ProcDefWith2DChannelArrayInConstructor) { + EXPECT_THAT(R"( +#![feature(explicit_state_access)] +#![feature(generics)] + +proc Node { + from_west: chan in, + to_east: chan out, +} + +impl Node { + fn new(from_west: chan in, to_east: chan out) -> Self { + Self { from_west, to_east } + } + fn next(self) { + let (tok, val) = recv(join(), self.from_west); + send(tok, self.to_east, val); + } +} + +proc Grid { + from_wests: chan[COLS + u32:1][ROWS] in, + to_easts: chan[COLS + u32:1][ROWS] out, +} + +impl Grid { + fn new() -> Self { + let (to_easts, from_wests) = chan[COLS + u32:1][ROWS]("east_west"); + unroll_for! (row, _): (u32, ()) in u32:0..ROWS { + unroll_for! (col, _): (u32, ()) in u32:0..COLS { + Node::new(from_wests[row][col], to_easts[row][col + u32:1]).spawn(); + }(()); + }(()); + Self { from_wests, to_easts } + } + fn next(self) { + const FIRST_COL = u32:0; + const LAST_COL = COLS; + unroll_for! (row, _): (u32, ()) in u32:0..ROWS { + send(join(), self.to_easts[row][FIRST_COL], row); + recv(join(), self.from_wests[row][LAST_COL]); + }(()); + } +} + +proc Top {} +impl Top { + fn new() -> Self { + Grid::new().spawn(); + Self {} + } +} +)", + TypecheckSucceeds(::testing::_)); +} } // namespace } // namespace xls::dslx diff --git a/xls/dslx/type_system_v2/unify_type_annotations.cc b/xls/dslx/type_system_v2/unify_type_annotations.cc index 96c36c9828..0cf2d9337c 100644 --- a/xls/dslx/type_system_v2/unify_type_annotations.cc +++ b/xls/dslx/type_system_v2/unify_type_annotations.cc @@ -224,7 +224,8 @@ class Unifier { return annotations[0]; } - return UnifyParametricStructOrProcAnnotations(*def, annotations_to_unify); + return UnifyParametricStructOrProcAnnotations(*def, annotations_to_unify, + span); } return UnifyBitsLikeTypeAnnotations(annotations, span); } @@ -346,16 +347,20 @@ class Unifier { "Array has ellipsis (`...`) but does not have a type annotation.", file_table_); } + const Span local_span = + ValidateSpanModule(span, module_) ? span : module_.span(); XLS_RETURN_IF_ERROR( - CheckArrayDimTooLarge(span, unified_dim->size, file_table_)); - XLS_ASSIGN_OR_RETURN(const TypeAnnotation* unified_element_type, - UnifyTypeAnnotations(element_type_annotations, span)); + CheckArrayDimTooLarge(local_span, unified_dim->size, file_table_)); + XLS_ASSIGN_OR_RETURN( + const TypeAnnotation* unified_element_type, + UnifyTypeAnnotations(element_type_annotations, local_span)); XLS_ASSIGN_OR_RETURN( Number * size_expr, - MakeTypeCheckedNumber(module_, table_, span, unified_dim->size, - CreateU32Annotation(module_, span))); + MakeTypeCheckedNumber(module_, table_, local_span, unified_dim->size, + CreateU32Annotation(module_, local_span))); return module_.Make( - span, const_cast(unified_element_type), size_expr); + local_span, const_cast(unified_element_type), + size_expr); } // Unifies multiple annotations for a function type. This assumes the @@ -468,11 +473,14 @@ class Unifier { if (!dims.has_value()) { return std::nullopt; } + const Span local_span = + ValidateSpanModule(span, module_) ? span : module_.span(); std::vector result(dims->size()); for (int i = 0; i < dims->size(); i++) { XLS_ASSIGN_OR_RETURN( - result[i], MakeTypeCheckedNumber(module_, table_, span, (*dims)[i], - CreateU32Annotation(module_, span))); + result[i], + MakeTypeCheckedNumber(module_, table_, local_span, (*dims)[i], + CreateU32Annotation(module_, local_span))); } return result; } @@ -500,8 +508,8 @@ class Unifier { // Unifies multiple annotations for a parametric struct, and produces an // annotation with agreeing explicit parametric values. absl::StatusOr UnifyParametricStructOrProcAnnotations( - const StructDefBase& def, - std::vector annotations) { + const StructDefBase& def, std::vector annotations, + const Span& span) { VLOG(6) << "Unifying parametric struct/proc annotations; def: " << def.identifier(); std::vector explicit_parametrics; @@ -540,10 +548,14 @@ class Unifier { bool match = false; if (value->IsTypeReference() && explicit_parametrics[i].IsTypeReference()) { + const Span unify_span = + ValidateSpanModule(annotation->span(), module_) + ? annotation->span() + : span; XLS_ASSIGN_OR_RETURN( std::optional unified, UnifyTypeReference(*value, explicit_parametrics[i], - annotation->span())); + unify_span)); if (unified.has_value()) { match = true; explicit_parametrics[i] = *unified; diff --git a/xls/examples/matmul_4x4/matmul_4x4.x b/xls/examples/matmul_4x4/matmul_4x4.x index 6efe544a59..6593708c95 100644 --- a/xls/examples/matmul_4x4/matmul_4x4.x +++ b/xls/examples/matmul_4x4/matmul_4x4.x @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// DSLX implementation of a 4x4 systolic array. +#![feature(explicit_state_access)] +#![feature(generics)] +// DSLX implementation of a 4x4 systolic array. import float32; @@ -23,27 +25,37 @@ struct NodeConfig { is_configured: bool, weight: F32 } // "node" implements the multiplier-accumulator PE (processing element) of the systolic array. proc node { - load_weight: chan in; - from_west: chan in; - from_north: chan in; - to_east: chan out; - to_south: chan out; - - config(load_weight: chan in, from_west: chan in, from_north: chan in, - to_east: chan out, to_south: chan out) { - (load_weight, from_west, from_north, to_east, to_south) - } + load_weight: chan in, + from_west: chan in, + from_north: chan in, + to_east: chan out, + to_south: chan out, + state: NodeConfig, +} - init { NodeConfig { is_configured: false, weight: float32::qnan() } } +impl node { + fn new + (load_weight: chan in, from_west: chan in, from_north: chan in, + to_east: chan out, to_south: chan out) -> Self { + Self { + load_weight, + from_west, + from_north, + to_east, + to_south, + state: NodeConfig { is_configured: false, weight: float32::qnan() }, + } + } - next(state: NodeConfig) { + fn next(self) { + let state = read(self.state); // If weight is undefined (NaN), receive it from the load weight channel. let (weight_tok, weight) = - recv_if(token(), load_weight, !state.is_configured, state.weight); + recv_if(token(), self.load_weight, !state.is_configured, state.weight); // Receive the activation from the west channel. - let (activation_tok, activation) = recv(weight_tok, from_west); + let (activation_tok, activation) = recv(weight_tok, self.from_west); // Receive the partial sum from the north channel. - let (partial_sum_tok, partial_sum) = recv(weight_tok, from_north); + let (partial_sum_tok, partial_sum) = recv(weight_tok, self.from_north); let input_tok = join(activation_tok, partial_sum_tok); // Multiply and accumulate. @@ -51,26 +63,30 @@ proc node { trace_fmt!("node[{}, {}]: fma(activation={}, weight={}, partial_sum={}) = {}", ROW, COL, activation, weight, partial_sum, result); // Forward the activation to the east channel. - send(input_tok, to_east, activation); + send(input_tok, self.to_east, activation); // Send the result to the south channel. - send(input_tok, to_south, result); - NodeConfig { is_configured: true, weight } + send(input_tok, self.to_south, result); + write(self.state, NodeConfig { is_configured: true, weight }); } } // "matmul" implements the systolic array as a network of ROWS x COLS PE nodes. proc matmul { - weights: chan in; - node_weights: chan[COLS][ROWS] out; - activations: chan[ROWS] in; - results: chan[COLS] out; - from_wests: chan[COLS + u32:1][ROWS] in; - to_easts: chan[COLS + u32:1][ROWS] out; - from_norths: chan[COLS][ROWS + u32:1] in; - to_souths: chan[COLS][ROWS + u32:1] out; - - config(weights: chan in, activations: chan[ROWS] in, - results: chan[COLS] out) { + weights: chan in, + node_weights: chan[COLS][ROWS] out, + activations: chan[ROWS] in, + results: chan[COLS] out, + from_wests: chan[COLS + u32:1][ROWS] in, + to_easts: chan[COLS + u32:1][ROWS] out, + from_norths: chan[COLS][ROWS + u32:1] in, + to_souths: chan[COLS][ROWS + u32:1] out, + weight_loaded: bool, +} + +impl matmul { + fn new + (weights: chan in, activations: chan[ROWS] in, + results: chan[COLS] out) -> Self { let (store_node_weights, load_node_weights) = chan[COLS][ROWS]("node_weights"); // Declare the east-to-west channels. let (to_easts, from_wests) = chan[COLS + u32:1][ROWS]("east_west"); @@ -78,28 +94,36 @@ proc matmul { let (to_souths, from_norths) = chan[COLS][ROWS + u32:1]("north_south"); unroll_for! (row, _): (u32, ()) in u32:0..ROWS { unroll_for! (col, _): (u32, ()) in u32:0..COLS { - spawn node( + node::new( load_node_weights[row][col], from_wests[row][col], from_norths[row][col], - to_easts[row][col + u32:1], to_souths[row + u32:1][col]); + to_easts[row][col + u32:1], to_souths[row + u32:1][col]).spawn( + ); }(()); }(()); - ( - weights, store_node_weights, activations, results, from_wests, to_easts, from_norths, + Self { + weights, + node_weights: store_node_weights, + activations, + results, + from_wests, + to_easts, + from_norths, to_souths, - ) + weight_loaded: false, + } } - init { false } + fn next(self) { + let weight_loaded = read(self.weight_loaded); - next(weight_loaded: bool) { // Receive the weights from the load weight channel. // Note: this is a one-off operation that happens on the first activation. let tok = if !weight_loaded { - let (recv_weights_tok, weights_matrix) = recv(token(), weights); + let (recv_weights_tok, weights_matrix) = recv(token(), self.weights); unroll_for! (row, tok): (u32, token) in u32:0..ROWS { unroll_for! (col, tok): (u32, token) in u32:0..COLS { - let weight_tok = - send(recv_weights_tok, node_weights[row][col], weights_matrix[row][col]); + let weight_tok = send( + recv_weights_tok, self.node_weights[row][col], weights_matrix[row][col]); join(weight_tok, tok) }(tok) }(token()) @@ -110,62 +134,62 @@ proc matmul { // Send activation to the "left"-end of the array. const ACTIVATIONS_COL = u32:0; unroll_for! (row, _): (u32, ()) in u32:0..ROWS { - let (tok, activation) = recv(tok, activations[row]); - send(tok, to_easts[row][ACTIVATIONS_COL], activation); + let (tok, activation) = recv(tok, self.activations[row]); + send(tok, self.to_easts[row][ACTIVATIONS_COL], activation); }(()); // Send zero values to the "top"-end of the array. const ZEROES_ROW = u32:0; unroll_for! (col, _): (u32, ()) in u32:0..COLS { - send(token(), to_souths[ZEROES_ROW][col], float32::zero(false)); + send(token(), self.to_souths[ZEROES_ROW][col], float32::zero(false)); }(()); // Consume and drop values on the "right"-end of the array. const DROPS_COL = COLS; unroll_for! (row, _): (u32, ()) in u32:0..ROWS { - recv(token(), from_wests[row][DROPS_COL]); + recv(token(), self.from_wests[row][DROPS_COL]); }(()); // Forward result from the "bottom"-end of the array. const RESULTS_ROW = ROWS; unroll_for! (col, _): (u32, ()) in u32:0..COLS { - let (tok, result) = recv(token(), from_norths[RESULTS_ROW][col]); - send(tok, results[col], result); + let (tok, result) = recv(token(), self.from_norths[RESULTS_ROW][col]); + send(tok, self.results[col], result); }(()); - - true + write(self.weight_loaded, true); } } // "matmul_4x4" is top-level proc that spawns the 4x4 matmul proc. -proc matmul_4x4 { - config(weights: chan in, activations: chan[4] in, results: chan[4] out) { - spawn matmul(weights, activations, results); +proc matmul_4x4 {} + +impl matmul_4x4 { + fn new + (weights: chan in, activations: chan[4] in, results: chan[4] out) + -> Self { + matmul::new(weights, activations, results).spawn(); + Self {} } - - init { () } - - next(state: ()) { } } -#[test_proc] +#[test] proc matmul_4x4_test { - weights: chan out; - activations: chan[4] out; - results: chan[4] in; - terminator: chan out; + weights: chan out, + activations: chan[4] out, + results: chan[4] in, + terminator: chan out, +} - config(terminator: chan out) { +impl matmul_4x4_test { + fn new(terminator: chan out) -> Self { let (weights_out, weights_in) = chan("weights"); let (activations_out, activations_in) = chan[4]("activations"); let (results_out, results_in) = chan[4]("results"); - spawn matmul_4x4(weights_in, activations_in, results_out); - (weights_out, activations_out, results_in, terminator) + matmul_4x4::new(weights_in, activations_in, results_out).spawn(); + Self { weights: weights_out, activations: activations_out, results: results_in, terminator } } - init { () } - - next(state: ()) { + fn next(self) { const F32_0 = float32::zero(u1:0); const F32_1 = float32::one(u1:0); const F32_2 = F32 { sign: u1:0, bexp: u8:128, fraction: u23:0 }; @@ -178,22 +202,22 @@ proc matmul_4x4_test { [F32_0, F32_0, F32_2, F32_0], [F32_0, F32_0, F32_0, F32_2], ]; - let tok = send(token(), weights, SCALAR_MATRIX_4X4_F32_2); + let tok = send(token(), self.weights, SCALAR_MATRIX_4X4_F32_2); - let tok = send(tok, activations[0], F32_1); - let tok = send(tok, activations[1], F32_2); - let tok = send(tok, activations[2], F32_3); - let tok = send(tok, activations[3], F32_4); + let tok = send(tok, self.activations[0], F32_1); + let tok = send(tok, self.activations[1], F32_2); + let tok = send(tok, self.activations[2], F32_3); + let tok = send(tok, self.activations[3], F32_4); - let (tok, value) = recv(tok, results[0]); + let (tok, value) = recv(tok, self.results[0]); assert_eq(value, F32_2); - let (tok, value) = recv(tok, results[1]); + let (tok, value) = recv(tok, self.results[1]); assert_eq(value, F32_4); - let (tok, value) = recv(tok, results[2]); + let (tok, value) = recv(tok, self.results[2]); assert_eq(value, F32_6); - let (tok, value) = recv(tok, results[3]); + let (tok, value) = recv(tok, self.results[3]); assert_eq(value, F32_8); - send(tok, terminator, true); + send(tok, self.terminator, true); } } diff --git a/xls/examples/memory_proc.x b/xls/examples/memory_proc.x index e93cb7e1cc..fe6a220441 100644 --- a/xls/examples/memory_proc.x +++ b/xls/examples/memory_proc.x @@ -16,6 +16,9 @@ // For a more comprehensive example that also can work with SRAM codegen, have // a look at ram.x +#![feature(explicit_state_access)] +#![feature(generics)] + pub const MEM_SIZE = u32:256; pub type MemWord = u32; @@ -31,60 +34,64 @@ type State = MemWord[MEM_SIZE]; const DEFAULT_VALUE = u32:0x12345678 as MemWord; pub proc Memory { - req_in: chan in; - data_out: chan out; - - config(req_in: chan in, data_out: chan out) { (req_in, data_out) } + req_in: chan in, + data_out: chan out, + state: State, +} - init { State:[DEFAULT_VALUE, ...] } +impl Memory { + fn new(req_in: chan in, data_out: chan out) -> Self { + Self { req_in, data_out, state: State:[DEFAULT_VALUE, ...] } + } - next(state: State) { + fn next(self) { + let state = read(self.state); let tok = join(); - let (tok, req) = recv(tok, req_in); + let (tok, req) = recv(tok, self.req_in); let state = if req.is_write { update(state, req.address, req.wdata) } else { state }; // A read request returns a response. - let tok = send_if(tok, data_out, !req.is_write, state[req.address]); - state + let tok = send_if(tok, self.data_out, !req.is_write, state[req.address]); + write(self.state, state); } } const TEST_WRITE_VALUE: u32 = u32:0xcafe600d; -#[test_proc] +#[test] proc MemoryTest { - terminator: chan out; - cmd_out: chan out; - read_data_in: chan in; + terminator: chan out, + cmd_out: chan out, + read_data_in: chan in, +} - config(terminator: chan out) { +impl MemoryTest { + fn new(terminator: chan out) -> Self { let (cmd_s, cmd_r) = chan("cmd"); let (data_s, data_r) = chan("data"); - spawn Memory(cmd_r, data_s); - (terminator, cmd_s, data_r) + Memory::new(cmd_r, data_s).spawn(); + Self { terminator, cmd_out: cmd_s, read_data_in: data_r } } - init { () } - - next(state: ()) { + fn next(self) { let tok = join(); // Read will yield default value in memory. let read_req = MemReq { is_write: false, address: u32:12, wdata: u32:0 }; - let tok = send(tok, cmd_out, read_req); - let (tok, value) = recv(tok, read_data_in); + let tok = send(tok, self.cmd_out, read_req); + let (tok, value) = recv(tok, self.read_data_in); assert_eq(value, DEFAULT_VALUE); // Write value to a particular address... let write_req = MemReq { is_write: true, address: u32:12, wdata: TEST_WRITE_VALUE }; - let tok = send(tok, cmd_out, write_req); + let tok = send(tok, self.cmd_out, write_req); // ... and expect it to be retrieved let read_req = MemReq { is_write: false, address: u32:12, wdata: u32:0 }; - let tok = send(tok, cmd_out, read_req); - let (tok, value) = recv(tok, read_data_in); + let tok = send(tok, self.cmd_out, read_req); + let (tok, value) = recv(tok, self.read_data_in); assert_eq(value, TEST_WRITE_VALUE); - let tok = send(tok, terminator, true); + let tok = send(tok, self.terminator, true); } }