1010from pycde .common import Constant , Input , Output
1111from pycde .constructs import ControlReg , Mux , Reg , Wire
1212from pycde .esi import ChannelService , FuncService , MMIO , MMIOReadWriteCmdType
13- from pycde .types import (Bits , Channel , ChannelSignaling , UInt )
13+ from pycde .types import (Bits , Channel , ChannelSignaling , StructType , UInt ,
14+ Window )
1415from pycde .handshake import Func
1516
1617import sys
@@ -137,6 +138,221 @@ def construct(ports):
137138 ChannelService .to_host (AppID ("join_x" ), f .x )
138139
139140
141+ # Define the struct with four fields
142+ FourFieldStruct = StructType ({
143+ "a" : Bits (32 ),
144+ "b" : Bits (32 ),
145+ "c" : Bits (32 ),
146+ "d" : Bits (32 ),
147+ })
148+
149+ # Create a window that divides the struct into two frames
150+ windowed_struct = Window (
151+ "four_field_window" , FourFieldStruct ,
152+ [Window .Frame ("frame1" , ["a" , "b" ]),
153+ Window .Frame ("frame2" , ["c" , "d" ])])
154+
155+
156+ class WindowToStructFunc (Module ):
157+ """Exposes a function that accepts a windowed struct (four fields split into
158+ two frames) and returns the reassembled struct without windowing.
159+
160+ The input struct has four UInt(32) fields: a, b, c, d.
161+ The window divides these into two frames:
162+ - Frame 1: fields a and b
163+ - Frame 2: fields c and d
164+
165+ Frames arrive in-order. The function reads both frames, reassembles the
166+ complete struct, and outputs it.
167+ """
168+
169+ clk = Clock ()
170+ rst = Reset ()
171+
172+ @generator
173+ def construct (ports ):
174+
175+ # Result is the complete struct (no windowing)
176+ result_chan = Wire (Channel (FourFieldStruct ))
177+ args = FuncService .get_call_chans (AppID ("struct_from_window" ),
178+ arg_type = windowed_struct ,
179+ result = result_chan )
180+
181+ # State register to track which frame we're expecting (0 = frame1, 1 = frame2)
182+ expecting_frame2 = Reg (Bits (1 ),
183+ name = "expecting_frame2" ,
184+ clk = ports .clk ,
185+ rst = ports .rst ,
186+ rst_value = 0 )
187+
188+ # Registers to hold the values from frame1
189+ a_reg = Reg (Bits (32 ),
190+ name = "a_reg" ,
191+ clk = ports .clk ,
192+ rst = ports .rst ,
193+ rst_value = 0 )
194+ b_reg = Reg (Bits (32 ),
195+ name = "b_reg" ,
196+ clk = ports .clk ,
197+ rst = ports .rst ,
198+ rst_value = 0 )
199+
200+ # Unwrap the incoming channel
201+ ready = Wire (Bits (1 ))
202+ window_data , window_valid = args .unwrap (ready )
203+
204+ # Unwrap the window to get the union of frames
205+ frame_union = window_data .unwrap ()
206+
207+ # Extract data from both frames (only one is valid at a time based on state)
208+ # Access the frame structs through the union - the data is reinterpreted
209+ # based on which frame we're expecting
210+ frame1_data = frame_union ["frame1" ]
211+ frame2_data = frame_union ["frame2" ]
212+
213+ # When we receive frame1, store a and b
214+ got_frame1 = window_valid & ~ expecting_frame2
215+ a_reg .assign (Mux (got_frame1 , a_reg , frame1_data .a ))
216+ b_reg .assign (Mux (got_frame1 , b_reg , frame1_data .b ))
217+
218+ # When we receive frame2, we can output the complete struct
219+ got_frame2 = window_valid & expecting_frame2
220+
221+ # Update state: after receiving frame1, expect frame2; after frame2, expect frame1
222+ expecting_frame2 .assign (
223+ Mux (window_valid , expecting_frame2 , ~ expecting_frame2 ))
224+
225+ # Output the reassembled struct when we have frame2
226+ output_struct = FourFieldStruct ({
227+ "a" : a_reg ,
228+ "b" : b_reg ,
229+ "c" : frame2_data ["c" ],
230+ "d" : frame2_data ["d" ]
231+ })
232+ result_internal , result_ready = Channel (FourFieldStruct ).wrap (
233+ output_struct , got_frame2 )
234+
235+ # We're ready to accept when either:
236+ # - We're waiting for frame1 (always ready)
237+ # - We're waiting for frame2 and downstream is ready
238+ ready .assign (~ expecting_frame2 | result_ready )
239+ result_chan .assign (result_internal )
240+
241+
242+ class StructToWindowFunc (Module ):
243+ """Exposes a function that accepts a complete struct and returns it as a
244+ windowed struct split into two frames.
245+
246+ This is the inverse of WindowedStructFunc.
247+
248+ The input struct has four Bits(32) fields: a, b, c, d.
249+ The output window divides these into two frames:
250+ - Frame 1: fields a and b
251+ - Frame 2: fields c and d
252+
253+ The function reads the complete struct, then outputs two frames in order.
254+ """
255+
256+ clk = Clock ()
257+ rst = Reset ()
258+
259+ @generator
260+ def construct (ports ):
261+ # Result is the windowed struct
262+ result_chan = Wire (Channel (windowed_struct ))
263+ args = FuncService .get_call_chans (AppID ("struct_to_window" ),
264+ arg_type = FourFieldStruct ,
265+ result = result_chan )
266+
267+ # State register to track which frame we're sending (0 = frame1, 1 = frame2)
268+ sending_frame2 = Reg (Bits (1 ),
269+ name = "sending_frame2" ,
270+ clk = ports .clk ,
271+ rst = ports .rst ,
272+ rst_value = 0 )
273+
274+ # Register to indicate we have a valid struct to send
275+ have_struct = Reg (Bits (1 ),
276+ name = "have_struct" ,
277+ clk = ports .clk ,
278+ rst = ports .rst ,
279+ rst_value = 0 )
280+
281+ # Registers to hold the input struct fields
282+ a_reg = Reg (Bits (32 ),
283+ name = "a_reg" ,
284+ clk = ports .clk ,
285+ rst = ports .rst ,
286+ rst_value = 0 )
287+ b_reg = Reg (Bits (32 ),
288+ name = "b_reg" ,
289+ clk = ports .clk ,
290+ rst = ports .rst ,
291+ rst_value = 0 )
292+ c_reg = Reg (Bits (32 ),
293+ name = "c_reg" ,
294+ clk = ports .clk ,
295+ rst = ports .rst ,
296+ rst_value = 0 )
297+ d_reg = Reg (Bits (32 ),
298+ name = "d_reg" ,
299+ clk = ports .clk ,
300+ rst = ports .rst ,
301+ rst_value = 0 )
302+
303+ # Unwrap the incoming channel
304+ ready = Wire (Bits (1 ))
305+ struct_data , struct_valid = args .unwrap (ready )
306+
307+ # Get the lowered type (a union of frame structs)
308+ lowered_type = windowed_struct .lowered_type
309+
310+ # Create frame1 and frame2 data
311+ frame1_struct = lowered_type .frame1 ({"a" : a_reg , "b" : b_reg })
312+ frame2_struct = lowered_type .frame2 ({"c" : c_reg , "d" : d_reg })
313+
314+ # Select which frame to output based on state
315+ frame1_union = lowered_type (("frame1" , frame1_struct ))
316+ frame2_union = lowered_type (("frame2" , frame2_struct ))
317+
318+ # Mux between frames based on state
319+ output_union = Mux (sending_frame2 , frame1_union , frame2_union )
320+ output_window = windowed_struct .wrap (output_union )
321+
322+ # Output is valid when we have a struct to send
323+ output_valid = have_struct
324+ result_internal , result_ready = Channel (windowed_struct ).wrap (
325+ output_window , output_valid )
326+
327+ # Compute state transitions
328+ frame_sent = output_valid & result_ready
329+ store_struct = struct_valid & ~ have_struct
330+ done_sending = frame_sent & sending_frame2
331+
332+ # Store the incoming struct when we receive it and aren't busy
333+ a_reg .assign (Mux (store_struct , a_reg , struct_data ["a" ]))
334+ b_reg .assign (Mux (store_struct , b_reg , struct_data ["b" ]))
335+ c_reg .assign (Mux (store_struct , c_reg , struct_data ["c" ]))
336+ d_reg .assign (Mux (store_struct , d_reg , struct_data ["d" ]))
337+
338+ # have_struct: set when storing, clear when done sending both frames
339+ have_struct .assign (
340+ Mux (store_struct , Mux (done_sending , have_struct ,
341+ Bits (1 )(0 )),
342+ Bits (1 )(1 )))
343+
344+ # sending_frame2: set after sending frame1, clear after sending frame2
345+ sending_frame2 .assign (
346+ Mux (frame_sent & ~ sending_frame2 ,
347+ Mux (done_sending , sending_frame2 ,
348+ Bits (1 )(0 )),
349+ Bits (1 )(1 )))
350+
351+ # We're ready to accept a new struct when we don't have one
352+ ready .assign (~ have_struct )
353+ result_chan .assign (result_internal )
354+
355+
140356class Top (Module ):
141357 clk = Clock ()
142358 rst = Reset ()
@@ -148,6 +364,8 @@ def construct(ports):
148364 MMIOClient (i )()
149365 MMIOReadWriteClient (clk = ports .clk , rst = ports .rst )
150366 ConstProducer (clk = ports .clk , rst = ports .rst )
367+ WindowToStructFunc (clk = ports .clk , rst = ports .rst )
368+ StructToWindowFunc (clk = ports .clk , rst = ports .rst )
151369
152370 # Disable broken test.
153371 # Join(clk=ports.clk, rst=ports.rst)
0 commit comments