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
751 if client_type.data.bitwidth == 0:
752 raise ValueError(
"Client data type cannot be zero-width. Use a "
753 "single-bit type if no data is needed.")
756 clk=ports.clk, rst=ports.rst, in_=demuxed_upstream_channel)
757 client_resp_channel = gearbox.out.transform(
lambda m: client_type({
759 "data": m.data.bitcast(client_type.data)
763 client_bundle, froms = client.type.pack(resp=client_resp_channel)
764 client_req = froms[
"req"]
765 tagged_client_req = client_req.transform(
766 lambda r: hostmem_module.UpstreamReadReq({
767 "address": r.address,
768 "length": (client_type.data.bitwidth + 7) // 8,
772 tagged_client_reqs.append(tagged_client_req)
775 setattr(ports, HostmemReadProcessorImpl.reqPortMap[client],
781 muxed_client_reqs = esi.ChannelMux(tagged_client_reqs)
782 upstream_req_channel.assign(muxed_client_reqs)
783 HostmemReadProcessorImpl.reqPortMap.clear()
785 return HostmemReadProcessorImpl
790 output_bitwidth: int) -> type[
"TaggedWriteGearboxImpl"]:
791 """Build a gearbox to convert the client data to upstream write chunks.
792 Assumes a struct {address, tag, data} and only gearboxes the data. Tag is
793 stored separately and the struct is re-assembled later on."""
795 if output_bitwidth % 8 != 0:
796 raise ValueError(
"Output bitwidth must be a multiple of 8.")
798 if input_bitwidth % 8 != 0:
799 input_pad_bits = 8 - (input_bitwidth % 8)
800 input_padded_bitwidth = input_bitwidth + input_pad_bits
802 class TaggedWriteGearboxImpl(Module):
807 (
"address", UInt(64)),
808 (
"tag", esi.HostMem.TagType),
809 (
"data", Bits(input_bitwidth)),
813 (
"address", UInt(64)),
814 (
"tag", esi.HostMem.TagType),
815 (
"data", Bits(output_bitwidth)),
816 (
"valid_bytes", Bits(8)),
819 num_chunks = ceil(input_padded_bitwidth / output_bitwidth)
823 upstream_ready = Wire(Bits(1))
824 ready_for_client = Wire(Bits(1))
825 client_tag_and_data, client_valid = ports.in_.unwrap(ready_for_client)
826 client_data = client_tag_and_data.data
827 if input_pad_bits > 0:
828 client_data = client_data.pad_or_truncate(input_padded_bitwidth)
829 client_xact = ready_for_client & client_valid
830 input_bitwidth_bytes = input_padded_bitwidth // 8
831 output_bitwidth_bytes = output_bitwidth // 8
835 if output_bitwidth == input_padded_bitwidth:
836 upstream_data_bits = client_data
837 upstream_valid = client_valid
838 ready_for_client.assign(upstream_ready)
839 tag = client_tag_and_data.tag
840 address = client_tag_and_data.address
841 valid_bytes = Bits(8)(input_bitwidth_bytes)
842 elif output_bitwidth > input_padded_bitwidth:
843 upstream_data_bits = client_data.as_bits(output_bitwidth)
844 upstream_valid = client_valid
845 ready_for_client.assign(upstream_ready)
846 tag = client_tag_and_data.tag
847 address = client_tag_and_data.address
848 valid_bytes = Bits(8)(input_bitwidth_bytes)
852 num_chunks = TaggedWriteGearboxImpl.num_chunks
853 num_chunks_idx_bitwidth = clog2(num_chunks)
854 if input_padded_bitwidth % output_bitwidth == 0:
857 padding_numbits = output_bitwidth - (input_padded_bitwidth %
859 client_data_padded = BitsSignal.concat(
860 [Bits(padding_numbits)(0), client_data])
862 client_data_padded[i * output_bitwidth:(i + 1) * output_bitwidth]
863 for i
in range(num_chunks)
865 chunk_regs = Array(Bits(output_bitwidth), num_chunks)([
866 c.reg(ports.clk, ce=client_xact, name=f
"chunk_{idx}")
867 for idx, c
in enumerate(chunks)
869 increment = Wire(Bits(1))
870 clear = Wire(Bits(1))
871 counter = Counter(num_chunks_idx_bitwidth)(clk=ports.clk,
875 upstream_data_bits = chunk_regs[counter.out]
876 upstream_valid = ControlReg(ports.clk, ports.rst, [client_xact],
878 upstream_xact = upstream_valid & upstream_ready
879 clear.assign(upstream_xact & (counter.out == (num_chunks - 1)))
880 increment.assign(upstream_xact)
881 ready_for_client.assign(~upstream_valid)
882 address_padding_bits = clog2(output_bitwidth_bytes)
883 counter_bytes = BitsSignal.concat(
884 [counter.out.as_bits(),
885 Bits(address_padding_bits)(0)]).as_uint()
888 tag_reg = client_tag_and_data.tag.reg(ports.clk,
891 addr_reg = client_tag_and_data.address.reg(ports.clk,
894 address = (addr_reg + counter_bytes).as_uint(64)
896 valid_bytes = Mux(counter.out == (num_chunks - 1),
897 Bits(8)(output_bitwidth_bytes),
898 Bits(8)((output_bitwidth - padding_numbits) // 8))
900 upstream_channel, upstrm_ready_sig = TaggedWriteGearboxImpl.out.type.wrap(
904 "data": upstream_data_bits,
905 "valid_bytes": valid_bytes
907 upstream_ready.assign(upstrm_ready_sig)
908 ports.out = upstream_channel
910 return TaggedWriteGearboxImpl
914def EmitEveryN(message_type: Type, N: int) -> type[
'EmitEveryNImpl']:
915 """Emit (forward) one message for every N input messages. The emitted message
916 is the last one of the N received. N must be >= 1."""
919 raise ValueError(
"N must be >= 1")
921 class EmitEveryNImpl(Module):
924 in_ = InputChannel(message_type)
925 out = OutputChannel(message_type)
929 ready_for_in = Wire(Bits(1))
930 in_data, in_valid = ports.in_.unwrap(ready_for_in)
931 xact = in_valid & ready_for_in
935 out_chan, out_ready = EmitEveryNImpl.out.type.wrap(in_data, in_valid)
936 ready_for_in.assign(out_ready)
940 counter_width = clog2(N)
942 clear = Wire(Bits(1))
943 counter = Counter(counter_width)(clk=ports.clk,
949 last_msg = in_data.reg(ports.clk, ports.rst, ce=xact, name=
"last_msg")
951 hit_last = (counter.out == UInt(counter_width)(N - 1)) & xact
952 out_valid = ControlReg(ports.clk, ports.rst, [hit_last], [clear])
954 out_chan, out_ready = EmitEveryNImpl.out.type.wrap(last_msg, out_valid)
956 ready_for_in.assign(~(out_valid & ~out_ready))
957 clear.assign(out_valid & out_ready)
961 return EmitEveryNImpl
965 write_width: int, hostmem_module,
966 reqs: List[esi._OutputBundleSetter]) -> type[
"HostMemWriteProcessorImpl"]:
967 """Construct a host memory write request module to orchestrate the the write
968 connections. Responsible for both gearboxing the data, multiplexing the
969 requests, reassembling out-of-order responses and routing the responses to the
972 Generate this module dynamically to allow for multiple write clients of
973 multiple types to be directly accomodated."""
975 class HostMemWriteProcessorImpl(Module):
981 reqPortMap: Dict[esi._OutputBundleSetter, str] = {}
983 name =
"client_" + req.client_name_str
984 locals()[name] = Output(req.type)
985 reqPortMap[req] = name
988 upstream = Output(hostmem_module.write.type)
997 req, _ = Channel(hostmem_module.UpstreamWriteReq).
wrap(
1004 write_bundle, _ = hostmem_module.write.type.pack(req=req)
1005 ports.upstream = write_bundle
1008 assert len(reqs) <= 256,
"More than 256 write clients not supported."
1010 upstream_req_channel = Wire(Channel(hostmem_module.UpstreamWriteReq))
1011 upstream_write_bundle, froms = hostmem_module.write.type.pack(
1012 req=upstream_req_channel)
1013 ports.upstream = upstream_write_bundle
1014 upstream_ack_tag = froms[
"ackTag"]
1016 demuxed_acks = esi.TaggedDemux(len(reqs), upstream_ack_tag.type)(
1017 clk=ports.clk, rst=ports.rst, in_=upstream_ack_tag)
1022 write_channels: List[ChannelSignal] = []
1023 for idx, req
in enumerate(reqs):
1025 reqch = [c.channel
for c
in req.type.channels
if c.name ==
'req'][0]
1026 client_type = reqch.inner_type
1027 if isinstance(client_type.data, Window):
1028 client_type = client_type.lowered_type
1031 write_req_bundle_type = esi.HostMem.write_req_bundle_type(
1033 input_flit_ack = Wire(upstream_ack_tag.type)
1034 bundle_sig, froms = write_req_bundle_type.pack(ackTag=input_flit_ack)
1037 gearbox_in_type = gearbox_mod.in_.type.inner_type
1038 tagged_client_req = froms[
"req"]
1039 bitcast_client_req = tagged_client_req.transform(
1040 lambda m: gearbox_in_type({
1042 "address": m.address,
1043 "data": m.data.bitcast(gearbox_in_type.data)
1047 gearbox = gearbox_mod(clk=ports.clk,
1049 in_=bitcast_client_req)
1050 write_channels.append(
1051 gearbox.out.transform(
lambda m: m.type({
1052 "address": m.address,
1055 "valid_bytes": m.valid_bytes
1060 ack_every_n =
EmitEveryN(upstream_ack_tag.type, gearbox_mod.num_chunks)(
1061 clk=clk, rst=rst, in_=demuxed_acks.get_out(idx))
1062 input_flit_ack.assign(ack_every_n.out)
1065 setattr(ports, HostMemWriteProcessorImpl.reqPortMap[req], bundle_sig)
1068 muxed_write_channel = esi.ChannelMux(write_channels)
1069 upstream_req_channel.assign(muxed_write_channel)
1071 return HostMemWriteProcessorImpl
1075def ChannelHostMem(read_width: int,
1076 write_width: int) -> typing.Type[
'ChannelHostMemImpl']:
1078 class ChannelHostMemImpl(esi.ServiceImplementation):
1079 """Builds a HostMem service which multiplexes multiple HostMem clients into
1080 two (read and write) bundles of the given data width."""
1085 UpstreamReadReq = StructType([
1086 (
"address", UInt(64)),
1087 (
"length", UInt(32)),
1092 BundledChannel(
"req", ChannelDirection.TO, UpstreamReadReq),
1094 "resp", ChannelDirection.FROM,
1096 (
"tag", esi.HostMem.TagType),
1097 (
"data", Bits(read_width)),
1101 if write_width % 8 != 0:
1102 raise ValueError(
"Write width must be a multiple of 8.")
1103 UpstreamWriteReq = StructType([
1104 (
"address", UInt(64)),
1106 (
"data", Bits(write_width)),
1107 (
"valid_bytes", Bits(8)),
1111 BundledChannel(
"req", ChannelDirection.TO, UpstreamWriteReq),
1112 BundledChannel(
"ackTag", ChannelDirection.FROM, UInt(8)),
1116 def generate(ports, bundles: esi._ServiceGeneratorBundles):
1120 read_reqs = [req
for req
in bundles.to_client_reqs
if req.port ==
'read']
1123 read_proc = read_proc_module(clk=ports.clk, rst=ports.rst)
1124 ports.read = read_proc.upstream
1125 for req
in read_reqs:
1126 req.assign(getattr(read_proc, read_proc_module.reqPortMap[req]))
1130 req
for req
in bundles.to_client_reqs
if req.port ==
'write'
1134 write_proc = write_proc_module(clk=ports.clk, rst=ports.rst)
1135 ports.write = write_proc.upstream
1136 for req
in write_reqs:
1137 req.assign(getattr(write_proc, write_proc_module.reqPortMap[req]))
1139 return ChannelHostMemImpl
1187def ChannelEngineService(
1188 to_host_engine_gen: Callable,
1189 from_host_engine_gen: Callable) -> type[
'ChannelEngineService']:
1190 """Returns a channel service implementation which calls
1191 to_host_engine_gen(<client_type>) or from_host_engine_gen(<client_type>) to
1192 generate the to_host and from_host engines for each channel. Does not support
1193 engines which can service multiple clients at once."""
1195 class ChannelEngineService(esi.ServiceImplementation):
1196 """Service implementation which services the clients via a per-channel DMA
1203 def build(ports, bundles: esi._ServiceGeneratorBundles):
1207 def build_engine_appid(client_appid: List[
esi.AppID],
1208 channel_name: str) -> str:
1209 appid_strings = [str(appid)
for appid
in client_appid]
1210 return f
"{'_'.join(appid_strings)}.{channel_name}"
1212 def build_engine(bc: BundledChannel, input_channel=
None) -> Type:
1213 idbase = build_engine_appid(bundle.client_name, bc.name)
1218 engine_client_type = bc.channel.inner_type
1219 is_void = (engine_client_type.bitwidth == 0)
1221 engine_client_type = Bits(8)
1222 if bc.direction == ChannelDirection.FROM:
1223 engine_mod = to_host_engine_gen(engine_client_type)
1225 engine_mod = from_host_engine_gen(engine_client_type)
1230 eng_details: Dict[str, object] = {
"engine_inst": eng_appid}
1231 if input_channel
is not None:
1235 input_channel = input_channel.transform(
lambda _: Bits(8)(0))
1236 if (engine_mod.input_channel.type.signaling
1237 != input_channel.type.signaling):
1238 input_channel = input_channel.buffer(
1242 output_signaling=engine_mod.input_channel.type.signaling)
1243 eng_inputs[
"input_channel"] = input_channel
1244 if hasattr(engine_mod,
"mmio"):
1245 mmio_appid =
esi.AppID(idbase +
".mmio")
1246 eng_inputs[
"mmio"] = esi.MMIO.read_write(mmio_appid)
1247 eng_details[
"mmio"] = mmio_appid
1248 if hasattr(engine_mod,
"hostmem_write"):
1249 eng_inputs[
"hostmem_write"] = esi.HostMem.write_from_bundle(
1251 engine_mod.hostmem_write.type)
1252 if hasattr(engine_mod,
"hostmem_read"):
1253 eng_inputs[
"hostmem_read"] = esi.HostMem.read_from_bundle(
1254 esi.AppID(idbase +
".hostmem_read"), engine_mod.hostmem_read.type)
1255 engine = engine_mod(appid=eng_appid, **eng_inputs)
1256 engine_rec = bundles.emit_engine(engine, details=eng_details)
1257 engine_rec.add_record(bundle, {bc.name: {}})
1260 for bundle
in bundles.to_client_reqs:
1261 bundle_type = bundle.type
1264 for bc
in bundle_type.channels:
1265 if bc.direction == ChannelDirection.TO:
1266 engine = build_engine(bc)
1267 out_chan = engine.output_channel
1269 if bc.channel.inner_type.bitwidth == 0:
1270 out_chan = out_chan.transform(
lambda _: Bits(0)(0))
1271 to_channels[bc.name] = out_chan
1273 client_bundle_sig, froms = bundle_type.pack(**to_channels)
1274 bundle.assign(client_bundle_sig)
1277 for bc
in bundle_type.channels:
1278 if bc.direction == ChannelDirection.FROM:
1279 build_engine(bc, froms[bc.name])
1281 return ChannelEngineService