64 """Construct the ESI header MMIO adhering to the MMIO layout specified in
65 the ChannelMMIO service implementation."""
69 read = Input(esi.MMIO.read.type)
73 data_chan_wire = Wire(Channel(esi.MMIODataType))
74 input_bundles = ports.read.unpack(data=data_chan_wire)
75 address_chan = input_bundles[
'offset']
77 address_ready = Wire(Bits(1))
78 address, address_valid = address_chan.unwrap(address_ready)
79 address_words = address.as_bits()[3:]
81 cycles = Counter(64)(clk=ports.clk,
85 instance_name=
"cycle_counter")
88 core_freq = System.current().core_freq
91 header = Array(Bits(64), 8)([
101 header.name =
"header"
102 header_response_valid = address_valid
104 header_out = header[address_words[:3]]
105 header_out.name =
"header_out"
107 data_chan, data_chan_ready = Channel(esi.MMIODataType).
wrap(
108 header_out, header_response_valid)
109 data_chan_wire.assign(data_chan)
110 address_ready.assign(data_chan_ready)
117 data_type: Type, num_outs: int,
118 next_sel_width: int) -> type[
"ChannelDemuxNImpl"]:
119 """N-way channel demultiplexer for valid/ready signaling. Contains
120 valid/ready registers on the output channels. The selection signal is now
121 embedded in the input channel payload as a struct {sel, data}. Input
122 signals ready when the selected output register is empty."""
124 assert num_outs >= 1,
"num_outs must be at least 1."
126 class ChannelDemuxNImpl(Module):
131 InPayloadType = StructType([
132 (
"sel", Bits(clog2(num_outs))),
133 (
"next_sel", Bits(next_sel_width)),
136 inp = Input(Channel(InPayloadType))
137 OutPayloadType = StructType([
138 (
"next_sel", Bits(next_sel_width)),
142 for i
in range(num_outs):
143 locals()[f
"output_{i}"] = Output(Channel(OutPayloadType))
146 def generate(ports) -> None:
151 sel_width = clog2(num_outs)
154 input_ready = Wire(Bits(1), name=
"input_ready")
155 in_payload, in_valid = ports.inp.unwrap(input_ready)
156 in_sel = in_payload.sel
157 in_next_sel = in_payload.next_sel
158 in_data = in_payload.data
163 valid_regs: List[BitsSignal] = []
164 selected_valid_expr = Bits(1)(0)
166 for i
in range(num_outs):
168 will_write = Wire(Bits(1), name=f
"will_write_{i}")
169 write_cond = (in_valid & input_ready & (in_sel == Bits(sel_width)(i)))
170 will_write.assign(write_cond)
173 out_msg_reg = ChannelDemuxNImpl.OutPayloadType({
174 "next_sel": in_next_sel,
176 }).reg(clk=clk, rst=rst, ce=will_write, name=f
"out{i}_msg_reg")
179 consume = Wire(Bits(1), name=f
"consume_{i}")
180 valid_reg = ControlReg(
183 asserts=[will_write],
185 name=f
"out{i}_valid_reg",
187 valid_regs.append(valid_reg)
190 ch_sig, ch_ready = Channel(ChannelDemuxNImpl.OutPayloadType).
wrap(
191 out_msg_reg, valid_reg)
192 setattr(ports, f
"output_{i}", ch_sig)
193 consume.assign(valid_reg & ch_ready)
196 selected_valid_expr = (selected_valid_expr | (
197 (in_sel == Bits(sel_width)(i)) & valid_reg)).as_bits()
200 input_ready.assign((selected_valid_expr ^ Bits(1)(1)).as_bits())
202 def get_out(self, index: int) -> ChannelSignal:
203 return getattr(self, f
"output_{index}")
205 return ChannelDemuxNImpl
210 data_type: Type, num_outs: int,
211 branching_factor_log2: int) -> type[
"ChannelDemuxTree"]:
212 """Pipelined N-way channel demultiplexer for valid/ready signaling. This
213 implementation uses a tree structure of
214 ChannelDemuxN_HalfStage_ReadyBlocking modules to reduce fanout pressure.
215 Supports maximum half-throughput to save complexity and area.
218 root_sel_width = clog2(num_outs)
220 num_outs = 2**root_sel_width
221 sel_width = branching_factor_log2
222 fanout = 2**sel_width
224 class ChannelDemuxTree(Module):
228 InPayloadType = StructType([
229 (
"sel", Bits(clog2(num_outs))),
232 inp = Input(Channel(InPayloadType))
235 for i
in range(num_outs):
236 locals()[f
"output_{i}"] = Output(Channel(data_type))
239 def build(ports) -> None:
240 assert branching_factor_log2 > 0
243 setattr(ports,
"output_0", ports.inp.transform(
lambda p: p.data))
246 def payload_type(sel_width: int, next_sel_width: int) -> Type:
248 (
"sel", Bits(sel_width)),
249 (
"next_sel", Bits(next_sel_width)),
253 def next_sel_width_calc(curr_sel_width) -> int:
254 return max(curr_sel_width - sel_width, 0)
256 def payload_next(curr_msg: StructSignal) -> StructSignal:
257 """Given current level payload, produce next level payload by
258 stripping off the top selection bits."""
260 next_sel_width = next_sel_width_calc(curr_msg.next_sel.type.width)
261 curr_sel_width = curr_msg.next_sel.type.width
262 new_sel_width = min(curr_sel_width, sel_width)
268 "sel": (curr_msg.next_sel[next_sel_width:]
269 if curr_sel_width > 0
else Bits(0)(0)),
270 "next_sel": (curr_msg.next_sel[:next_sel_width]
271 if next_sel_width > 0
else Bits(0)(0)),
272 "data": curr_msg.data,
275 current_channels: List[ChannelSignal] = [
276 ports.inp.transform(
lambda m: payload_type(0, root_sel_width)({
283 curr_sel_width = root_sel_width
285 while len(current_channels) < num_outs:
286 next_level: List[ChannelSignal] = []
287 level_num_outs = min(2**curr_sel_width, fanout)
288 for i, c
in enumerate(current_channels):
291 num_outs=level_num_outs,
292 next_sel_width=next_sel_width_calc(curr_sel_width),
296 inp=c.transform(payload_next),
297 instance_name=f
"demux_l{level}_i{i}",
299 for j
in range(level_num_outs):
300 next_level.append(dmux.get_out(j))
301 current_channels = next_level
302 curr_sel_width -= sel_width
305 for i
in range(num_outs):
310 current_channels[i].transform(
lambda p: p.data),
313 def get_out(self, index: int) -> ChannelSignal:
314 return getattr(self, f
"output_{index}")
316 return ChannelDemuxTree
593 output_bitwidth: int) -> type[
"TaggedReadGearboxImpl"]:
594 """Build a gearbox to convert the upstream data to the client data
595 type. Assumes a struct {tag, data} and only gearboxes the data. Tag is stored
596 separately and the struct is re-assembled later on."""
598 class TaggedReadGearboxImpl(Module):
603 (
"tag", esi.HostMem.TagType),
604 (
"data", Bits(input_bitwidth)),
608 (
"tag", esi.HostMem.TagType),
609 (
"data", Bits(output_bitwidth)),
614 ready_for_upstream = Wire(Bits(1), name=
"ready_for_upstream")
615 upstream_tag_and_data, upstream_valid = ports.in_.unwrap(
617 upstream_data = upstream_tag_and_data.data
618 upstream_xact = ready_for_upstream & upstream_valid
622 if output_bitwidth == input_bitwidth:
623 client_data_bits = upstream_data
624 client_valid = upstream_valid
625 elif output_bitwidth < input_bitwidth:
626 client_data_bits = upstream_data[:output_bitwidth]
627 client_valid = upstream_valid
632 chunks = ceil(output_bitwidth / input_bitwidth)
633 reg_ces = [Wire(Bits(1))
for _
in range(chunks)]
635 upstream_data.reg(ports.clk,
638 name=f
"chunk_reg_{idx}")
for idx
in range(chunks)
640 client_data_bits = BitsSignal.concat(reversed(regs))[:output_bitwidth]
644 clear_counter = Wire(Bits(1))
645 counter_width = clog2(chunks)
646 counter = Counter(counter_width)(clk=ports.clk,
649 increment=upstream_xact)
650 set_client_valid = counter.out == chunks - 1
651 client_xact = Wire(Bits(1))
652 client_valid = ControlReg(ports.clk, ports.rst,
653 [set_client_valid & upstream_xact],
655 client_xact.assign(client_valid & ready_for_upstream)
656 clear_counter.assign(client_xact)
657 for idx, reg_ce
in enumerate(reg_ces):
658 reg_ce.assign(upstream_xact &
659 (counter.out == UInt(counter_width)(idx)))
662 tag_reg = upstream_tag_and_data.tag.reg(ports.clk,
667 client_channel, client_ready = TaggedReadGearboxImpl.out.type.wrap(
670 "data": client_data_bits,
672 ready_for_upstream.assign(client_ready)
673 ports.out = client_channel
675 return TaggedReadGearboxImpl
679 reqs: List[esi._OutputBundleSetter]):
680 """Construct a host memory read request module to orchestrate the the read
681 connections. Responsible for both gearboxing the data, multiplexing the
682 requests, reassembling out-of-order responses and routing the responses to the
685 Generate this module dynamically to allow for multiple read clients of
686 multiple types to be directly accomodated."""
688 class HostmemReadProcessorImpl(Module):
693 reqPortMap: Dict[esi._OutputBundleSetter, str] = {}
695 name =
"client_" + req.client_name_str
696 locals()[name] = Output(req.type)
697 reqPortMap[req] = name
700 upstream = Output(hostmem_module.read.type)
704 """Build the read side of the HostMem service."""
708 upstream_req_channel, _ = Channel(hostmem_module.UpstreamReadReq).
wrap(
714 upstream_read_bundle, _ = hostmem_module.read.type.pack(
715 req=upstream_req_channel)
716 ports.upstream = upstream_read_bundle
723 assert len(reqs) <= 256,
"More than 256 read clients not supported."
726 upstream_req_channel = Wire(Channel(hostmem_module.UpstreamReadReq))
727 upstream_read_bundle, froms = hostmem_module.read.type.pack(
728 req=upstream_req_channel)
729 ports.upstream = upstream_read_bundle
730 upstream_resp_channel = froms[
"resp"]
732 demux = esi.TaggedDemux(len(reqs), upstream_resp_channel.type)(
733 clk=ports.clk, rst=ports.rst, in_=upstream_resp_channel)
735 tagged_client_reqs = []
736 for idx, client
in enumerate(reqs):
739 c.channel
for c
in client.type.channels
if c.name ==
'resp'
741 demuxed_upstream_channel = demux.get_out(idx)
750 client_type = resp_type.inner_type
752 clk=ports.clk, rst=ports.rst, in_=demuxed_upstream_channel)
753 client_resp_channel = gearbox.out.transform(
lambda m: client_type({
755 "data": m.data.bitcast(client_type.data)
759 client_bundle, froms = client.type.pack(resp=client_resp_channel)
760 client_req = froms[
"req"]
761 tagged_client_req = client_req.transform(
762 lambda r: hostmem_module.UpstreamReadReq({
763 "address": r.address,
764 "length": (client_type.data.bitwidth + 7) // 8,
768 tagged_client_reqs.append(tagged_client_req)
771 setattr(ports, HostmemReadProcessorImpl.reqPortMap[client],
777 muxed_client_reqs = esi.ChannelMux(tagged_client_reqs)
778 upstream_req_channel.assign(muxed_client_reqs)
779 HostmemReadProcessorImpl.reqPortMap.clear()
781 return HostmemReadProcessorImpl
786 output_bitwidth: int) -> type[
"TaggedWriteGearboxImpl"]:
787 """Build a gearbox to convert the client data to upstream write chunks.
788 Assumes a struct {address, tag, data} and only gearboxes the data. Tag is
789 stored separately and the struct is re-assembled later on."""
791 if output_bitwidth % 8 != 0:
792 raise ValueError(
"Output bitwidth must be a multiple of 8.")
794 if input_bitwidth % 8 != 0:
795 input_pad_bits = 8 - (input_bitwidth % 8)
796 input_padded_bitwidth = input_bitwidth + input_pad_bits
798 class TaggedWriteGearboxImpl(Module):
803 (
"address", UInt(64)),
804 (
"tag", esi.HostMem.TagType),
805 (
"data", Bits(input_bitwidth)),
809 (
"address", UInt(64)),
810 (
"tag", esi.HostMem.TagType),
811 (
"data", Bits(output_bitwidth)),
812 (
"valid_bytes", Bits(8)),
815 num_chunks = ceil(input_padded_bitwidth / output_bitwidth)
819 upstream_ready = Wire(Bits(1))
820 ready_for_client = Wire(Bits(1))
821 client_tag_and_data, client_valid = ports.in_.unwrap(ready_for_client)
822 client_data = client_tag_and_data.data
823 if input_pad_bits > 0:
824 client_data = client_data.pad_or_truncate(input_padded_bitwidth)
825 client_xact = ready_for_client & client_valid
826 input_bitwidth_bytes = input_padded_bitwidth // 8
827 output_bitwidth_bytes = output_bitwidth // 8
831 if output_bitwidth == input_padded_bitwidth:
832 upstream_data_bits = client_data
833 upstream_valid = client_valid
834 ready_for_client.assign(upstream_ready)
835 tag = client_tag_and_data.tag
836 address = client_tag_and_data.address
837 valid_bytes = Bits(8)(input_bitwidth_bytes)
838 elif output_bitwidth > input_padded_bitwidth:
839 upstream_data_bits = client_data.as_bits(output_bitwidth)
840 upstream_valid = client_valid
841 ready_for_client.assign(upstream_ready)
842 tag = client_tag_and_data.tag
843 address = client_tag_and_data.address
844 valid_bytes = Bits(8)(input_bitwidth_bytes)
848 num_chunks = TaggedWriteGearboxImpl.num_chunks
849 num_chunks_idx_bitwidth = clog2(num_chunks)
850 if input_padded_bitwidth % output_bitwidth == 0:
853 padding_numbits = output_bitwidth - (input_padded_bitwidth %
855 client_data_padded = BitsSignal.concat(
856 [Bits(padding_numbits)(0), client_data])
858 client_data_padded[i * output_bitwidth:(i + 1) * output_bitwidth]
859 for i
in range(num_chunks)
861 chunk_regs = Array(Bits(output_bitwidth), num_chunks)([
862 c.reg(ports.clk, ce=client_xact, name=f
"chunk_{idx}")
863 for idx, c
in enumerate(chunks)
865 increment = Wire(Bits(1))
866 clear = Wire(Bits(1))
867 counter = Counter(num_chunks_idx_bitwidth)(clk=ports.clk,
871 upstream_data_bits = chunk_regs[counter.out]
872 upstream_valid = ControlReg(ports.clk, ports.rst, [client_xact],
874 upstream_xact = upstream_valid & upstream_ready
875 clear.assign(upstream_xact & (counter.out == (num_chunks - 1)))
876 increment.assign(upstream_xact)
877 ready_for_client.assign(~upstream_valid)
878 address_padding_bits = clog2(output_bitwidth_bytes)
879 counter_bytes = BitsSignal.concat(
880 [counter.out.as_bits(),
881 Bits(address_padding_bits)(0)]).as_uint()
884 tag_reg = client_tag_and_data.tag.reg(ports.clk,
887 addr_reg = client_tag_and_data.address.reg(ports.clk,
890 address = (addr_reg + counter_bytes).as_uint(64)
892 valid_bytes = Mux(counter.out == (num_chunks - 1),
893 Bits(8)(output_bitwidth_bytes),
894 Bits(8)((output_bitwidth - padding_numbits) // 8))
896 upstream_channel, upstrm_ready_sig = TaggedWriteGearboxImpl.out.type.wrap(
900 "data": upstream_data_bits,
901 "valid_bytes": valid_bytes
903 upstream_ready.assign(upstrm_ready_sig)
904 ports.out = upstream_channel
906 return TaggedWriteGearboxImpl
910def EmitEveryN(message_type: Type, N: int) -> type[
'EmitEveryNImpl']:
911 """Emit (forward) one message for every N input messages. The emitted message
912 is the last one of the N received. N must be >= 1."""
915 raise ValueError(
"N must be >= 1")
917 class EmitEveryNImpl(Module):
920 in_ = InputChannel(message_type)
921 out = OutputChannel(message_type)
925 ready_for_in = Wire(Bits(1))
926 in_data, in_valid = ports.in_.unwrap(ready_for_in)
927 xact = in_valid & ready_for_in
931 out_chan, out_ready = EmitEveryNImpl.out.type.wrap(in_data, in_valid)
932 ready_for_in.assign(out_ready)
936 counter_width = clog2(N)
938 clear = Wire(Bits(1))
939 counter = Counter(counter_width)(clk=ports.clk,
945 last_msg = in_data.reg(ports.clk, ports.rst, ce=xact, name=
"last_msg")
947 hit_last = (counter.out == UInt(counter_width)(N - 1)) & xact
948 out_valid = ControlReg(ports.clk, ports.rst, [hit_last], [clear])
950 out_chan, out_ready = EmitEveryNImpl.out.type.wrap(last_msg, out_valid)
952 ready_for_in.assign(~(out_valid & ~out_ready))
953 clear.assign(out_valid & out_ready)
957 return EmitEveryNImpl
961 write_width: int, hostmem_module,
962 reqs: List[esi._OutputBundleSetter]) -> type[
"HostMemWriteProcessorImpl"]:
963 """Construct a host memory write request module to orchestrate the the write
964 connections. Responsible for both gearboxing the data, multiplexing the
965 requests, reassembling out-of-order responses and routing the responses to the
968 Generate this module dynamically to allow for multiple write clients of
969 multiple types to be directly accomodated."""
971 class HostMemWriteProcessorImpl(Module):
977 reqPortMap: Dict[esi._OutputBundleSetter, str] = {}
979 name =
"client_" + req.client_name_str
980 locals()[name] = Output(req.type)
981 reqPortMap[req] = name
984 upstream = Output(hostmem_module.write.type)
993 req, _ = Channel(hostmem_module.UpstreamWriteReq).
wrap(
1000 write_bundle, _ = hostmem_module.write.type.pack(req=req)
1001 ports.upstream = write_bundle
1004 assert len(reqs) <= 256,
"More than 256 write clients not supported."
1006 upstream_req_channel = Wire(Channel(hostmem_module.UpstreamWriteReq))
1007 upstream_write_bundle, froms = hostmem_module.write.type.pack(
1008 req=upstream_req_channel)
1009 ports.upstream = upstream_write_bundle
1010 upstream_ack_tag = froms[
"ackTag"]
1012 demuxed_acks = esi.TaggedDemux(len(reqs), upstream_ack_tag.type)(
1013 clk=ports.clk, rst=ports.rst, in_=upstream_ack_tag)
1018 write_channels: List[ChannelSignal] = []
1019 for idx, req
in enumerate(reqs):
1021 reqch = [c.channel
for c
in req.type.channels
if c.name ==
'req'][0]
1022 client_type = reqch.inner_type
1023 if isinstance(client_type.data, Window):
1024 client_type = client_type.lowered_type
1027 write_req_bundle_type = esi.HostMem.write_req_bundle_type(
1029 input_flit_ack = Wire(upstream_ack_tag.type)
1030 bundle_sig, froms = write_req_bundle_type.pack(ackTag=input_flit_ack)
1033 gearbox_in_type = gearbox_mod.in_.type.inner_type
1034 tagged_client_req = froms[
"req"]
1035 bitcast_client_req = tagged_client_req.transform(
1036 lambda m: gearbox_in_type({
1038 "address": m.address,
1039 "data": m.data.bitcast(gearbox_in_type.data)
1043 gearbox = gearbox_mod(clk=ports.clk,
1045 in_=bitcast_client_req)
1046 write_channels.append(
1047 gearbox.out.transform(
lambda m: m.type({
1048 "address": m.address,
1051 "valid_bytes": m.valid_bytes
1056 ack_every_n =
EmitEveryN(upstream_ack_tag.type, gearbox_mod.num_chunks)(
1057 clk=clk, rst=rst, in_=demuxed_acks.get_out(idx))
1058 input_flit_ack.assign(ack_every_n.out)
1061 setattr(ports, HostMemWriteProcessorImpl.reqPortMap[req], bundle_sig)
1064 muxed_write_channel = esi.ChannelMux(write_channels)
1065 upstream_req_channel.assign(muxed_write_channel)
1067 return HostMemWriteProcessorImpl
1071def ChannelHostMem(read_width: int,
1072 write_width: int) -> typing.Type[
'ChannelHostMemImpl']:
1074 class ChannelHostMemImpl(esi.ServiceImplementation):
1075 """Builds a HostMem service which multiplexes multiple HostMem clients into
1076 two (read and write) bundles of the given data width."""
1081 UpstreamReadReq = StructType([
1082 (
"address", UInt(64)),
1083 (
"length", UInt(32)),
1088 BundledChannel(
"req", ChannelDirection.TO, UpstreamReadReq),
1090 "resp", ChannelDirection.FROM,
1092 (
"tag", esi.HostMem.TagType),
1093 (
"data", Bits(read_width)),
1097 if write_width % 8 != 0:
1098 raise ValueError(
"Write width must be a multiple of 8.")
1099 UpstreamWriteReq = StructType([
1100 (
"address", UInt(64)),
1102 (
"data", Bits(write_width)),
1103 (
"valid_bytes", Bits(8)),
1107 BundledChannel(
"req", ChannelDirection.TO, UpstreamWriteReq),
1108 BundledChannel(
"ackTag", ChannelDirection.FROM, UInt(8)),
1112 def generate(ports, bundles: esi._ServiceGeneratorBundles):
1116 read_reqs = [req
for req
in bundles.to_client_reqs
if req.port ==
'read']
1119 read_proc = read_proc_module(clk=ports.clk, rst=ports.rst)
1120 ports.read = read_proc.upstream
1121 for req
in read_reqs:
1122 req.assign(getattr(read_proc, read_proc_module.reqPortMap[req]))
1126 req
for req
in bundles.to_client_reqs
if req.port ==
'write'
1130 write_proc = write_proc_module(clk=ports.clk, rst=ports.rst)
1131 ports.write = write_proc.upstream
1132 for req
in write_reqs:
1133 req.assign(getattr(write_proc, write_proc_module.reqPortMap[req]))
1135 return ChannelHostMemImpl
1183def ChannelEngineService(
1184 to_host_engine_gen: Callable,
1185 from_host_engine_gen: Callable) -> type[
'ChannelEngineService']:
1186 """Returns a channel service implementation which calls
1187 to_host_engine_gen(<client_type>) or from_host_engine_gen(<client_type>) to
1188 generate the to_host and from_host engines for each channel. Does not support
1189 engines which can service multiple clients at once."""
1191 class ChannelEngineService(esi.ServiceImplementation):
1192 """Service implementation which services the clients via a per-channel DMA
1199 def build(ports, bundles: esi._ServiceGeneratorBundles):
1203 def build_engine_appid(client_appid: List[
esi.AppID],
1204 channel_name: str) -> str:
1205 appid_strings = [str(appid)
for appid
in client_appid]
1206 return f
"{'_'.join(appid_strings)}.{channel_name}"
1208 def build_engine(bc: BundledChannel, input_channel=
None) -> Type:
1209 idbase = build_engine_appid(bundle.client_name, bc.name)
1211 if bc.direction == ChannelDirection.FROM:
1212 engine_mod = to_host_engine_gen(bc.channel.inner_type)
1214 engine_mod = from_host_engine_gen(bc.channel.inner_type)
1219 eng_details: Dict[str, object] = {
"engine_inst": eng_appid}
1220 if input_channel
is not None:
1221 if (engine_mod.input_channel.type.signaling
1222 != input_channel.type.signaling):
1223 input_channel = input_channel.buffer(
1227 output_signaling=engine_mod.input_channel.type.signaling)
1228 eng_inputs[
"input_channel"] = input_channel
1229 if hasattr(engine_mod,
"mmio"):
1230 mmio_appid =
esi.AppID(idbase +
".mmio")
1231 eng_inputs[
"mmio"] = esi.MMIO.read_write(mmio_appid)
1232 eng_details[
"mmio"] = mmio_appid
1233 if hasattr(engine_mod,
"hostmem_write"):
1234 eng_inputs[
"hostmem_write"] = esi.HostMem.write_from_bundle(
1236 engine_mod.hostmem_write.type)
1237 if hasattr(engine_mod,
"hostmem_read"):
1238 eng_inputs[
"hostmem_read"] = esi.HostMem.read_from_bundle(
1239 esi.AppID(idbase +
".hostmem_read"), engine_mod.hostmem_read.type)
1240 engine = engine_mod(appid=eng_appid, **eng_inputs)
1241 engine_rec = bundles.emit_engine(engine, details=eng_details)
1242 engine_rec.add_record(bundle, {bc.name: {}})
1245 for bundle
in bundles.to_client_reqs:
1246 bundle_type = bundle.type
1249 for bc
in bundle_type.channels:
1250 if bc.direction == ChannelDirection.TO:
1251 engine = build_engine(bc)
1252 to_channels[bc.name] = engine.output_channel
1254 client_bundle_sig, froms = bundle_type.pack(**to_channels)
1255 bundle.assign(client_bundle_sig)
1258 for bc
in bundle_type.channels:
1259 if bc.direction == ChannelDirection.FROM:
1260 build_engine(bc, froms[bc.name])
1262 return ChannelEngineService