class MyMod: in_port = Input(dim(8)) out0 = Output(dim(5)) out1 = Output(dim(5)) @generator def construct(mod): # Partial lower slice mod.out0 = mod.in_port[3:] # partial upper slice mod.out1 = mod.in_port[:5]
class FSMUser: a = Input(types.i1) b = Input(types.i1) c = Input(types.i1) clk = Input(types.i1) is_a = Output(types.i1) is_b = Output(types.i1) is_c = Output(types.i1) @generator def construct(ports): fsm = FSM(a=ports.a, b=ports.b, c=ports.c, clk=ports.clk) ports.is_a = fsm.is_A ports.is_b = fsm.is_B ports.is_c = fsm.is_C
class Slicing: In = Input(dim(8, 4, 5)) Sel8 = Input(types.i8) Sel2 = Input(types.i2) OutIntSlice = Output(types.i2) OutArrSlice8 = Output(dim(8, 4, 2)) OutArrSlice2 = Output(dim(8, 4, 2)) @generator def create(ports): i = ports.In[0][0] ports.OutIntSlice = i.slice(ports.Sel2, 2) ports.OutArrSlice2 = ports.In.slice(ports.Sel2, 2) ports.OutArrSlice8 = ports.In.slice(ports.Sel8, 2)
class M5: in0 = Input(dim(types.i32, 16)) in1 = Input(types.i32) t_c = dim(types.i32, 16) c = Output(t_c) @generator def build(ports): # a 32x32xi1 ndarray. # A dtype of i32 is fairly expensive wrt. the size of the output IR, but # but allows for assigning indiviudal bits. m = NDArray((32, 32), dtype=types.i1, name='m1') # Assign individual bits to the first 32 bits. for i in range(32): m[0, i] = hw.ConstantOp(types.i1, 1) # Fill the next 15 values with an i32. The ndarray knows how to convert # from i32 to <32xi1> to comply with the ndarray dtype. for i in range(1, 16): m[i] = ports.in1 # Fill the upportmost 16 rows with the input array of in0 : 16xi32 m[16:32] = ports.in0 # We don't provide a method of reshaping the ndarray wrt. its dtype. # that is: 32x32xi1 => 32xi32 # This has massive overhead in the generated IR, and can be easily # achieved by a bitcast. ports.c = hw.BitcastOp(M5.t_c, m.to_circt())
class M1: in1 = Input(dim(types.i32, 4, 8)) out = Output(dim(types.i32, 2, 16)) @generator def build(ports): ports.out = ports.in1.transpose((1, 0)).reshape((16, 2))
class M1: in1 = Input(dim(types.i32, 4, 8)) out = Output(dim(types.i32, 8, 4)) @generator def build(ports): ports.out = ports.in1.transpose((1, 0))
class M1: in1 = Input(dim(types.i32, 10)) out = Output(dim(types.i32, 10)) @generator def build(ports): ports.out = ports.in1.roll(3)
class WireNames: clk = Input(types.i1) sel = Input(types.i2) data_in = Input(dim(32, 3)) a = Output(types.i32) b = Output(types.i32) @generator def build(ports): foo = ports.data_in[0] foo.name = "foo" arr_data = dim(32, 4)([1, 2, 3, 4], "arr_data") ports.set_all_ports({ 'a': foo.reg(ports.clk).reg(ports.clk), 'b': arr_data[ports.sel], })
class M1: in1 = Input(dim(types.i32, 10)) out = Output(dim(types.i32, 10)) @generator def build(ports): m = NDArray(from_value=ports.in1, name='m1') ports.out = m.to_circt(create_wire=False)
class Plus: a = Input(types.i32) b = Input(types.i32) y = Output(types.i32) def __init__(self, name: str = None) -> None: if name is not None: self.instance_name = name
class ComplexPorts: clk = Input(types.i1) sel = Input(types.i2) data_in = Input(dim(32, 3)) struct_data_in = Input(types.struct({"foo": types.i36})) a = Output(types.i32) b = Output(types.i32) c = Output(types.i32) @generator def build(ports): assert len(ports.data_in) == 3 ports.set_all_ports({ 'a': ports.data_in[0].reg(ports.clk).reg(ports.clk), 'b': ports.data_in[ports.sel], 'c': ports.struct_data_in.foo[:-4] })
class TopLevel: x = Input(types.i32) y = Output(types.i32) @generator def construct(mod): TopLevel.part1 = DesignPartition("part1") Plus("Plus1", a=mod.x, b=mod.x, partition=TopLevel.part1) mod.y = PlusPipeline(a=mod.x).y
class PlusPipeline: a = Input(types.i32) y = Output(types.i32) @generator def construct(mod): p1 = Plus(a=mod.a, b=mod.a) p2 = Plus(a=p1.y, b=mod.a, partition=TopLevel.part1) p3 = Plus(a=p2.y, b=mod.a) mod.y = p3.y
class M1: out = Output(dim(types.i32, 3, 3)) @generator def build(ports): m = NDArray((3, 3), dtype=types.i32) for i in range(3): for j in range(3): m[i][j] = types.i32(i * 3 + j) ports.out = m.to_circt()
class CompReg: clk = Clock() input = Input(types.i8) output = Output(types.i8) @generator def build(ports): with ports.clk: compreg = ports.input.reg(name="reg", sv_attributes=["dont_merge"]) compreg.appid = AppID("reg", 0) ports.output = compreg
class ComplexMux: Clk = Input(dim(1)) In = Input(dim(3, 4, 5)) Sel = Input(dim(1)) Out = Output(dim(3, 4)) OutArr = Output(dim(3, 4, 2)) OutSlice = Output(dim(3, 4, 3)) OutInt = Output(types.i1) @generator def create(ports): clk = ports.Clk select_from = Value([ports.In[3].reg(clk).reg(clk, cycles=2), ports.In[1]]) ports.Out = select_from[ports.Sel] ports.OutArr = array_from_tuple(ports.In[0], ports.In[1]) ports.OutSlice = ports.In[0:3] ports.OutInt = ports.In[0][0][ports.Sel]
class Mux: clk = Clock() data = Input(dim(8, 14)) sel = Input(types.i4) out = Output(types.i8) @generator def build(ports): sel_reg = ports.sel.reg() ports.out = ports.data.reg()[sel_reg].reg()
class M1: in1 = Input(dim(types.i32, 10)) in2 = Input(dim(types.i32, 10)) in3 = Input(dim(types.i32, 10)) out = Output(dim(types.i32, 30)) @generator def build(ports): # Explicit ndarray. m = NDArray(from_value=ports.in1, name='m1') # Here we could do a sequence of transformations on 'm'... # Finally, concatenate [in2, m, in3] ports.out = ports.in2.concatenate((m, ports.in3))
class CompReg: clk = Input(types.i1) input = Input(types.i8) output = Output(types.i8) @generator def build(ports): compreg = seq.CompRegOp(types.i8, clk=ports.clk, input=ports.input, name="reg", sym_name="reg") ports.output = compreg
class M2: in0 = Input(dim(types.i32, 16)) in1 = Input(types.i32) t_c = dim(types.i32, 8, 4) c = Output(t_c) @generator def build(ports): # a 32xi32 ndarray. m = NDArray((32, ), dtype=types.i32, name='m2') for i in range(16): m[i] = ports.in1 m[16:32] = ports.in0 m = m.reshape((4, 8)) ports.c = m.to_circt()
class Mod: inp = Input(dim(SIZE)) out = Output(dim(SIZE)) @generator def construct(mod): c1 = hw.ConstantOp(dim(SIZE), 1) # CHECK: %[[EQ:.+]] = comb.icmp eq eq = comb.EqOp(c1, mod.inp) # CHECK: %[[A1:.+]] = hw.array_create %[[EQ]], %[[EQ]] a1 = hw.ArrayCreateOp([eq, eq]) # CHECK: %[[A2:.+]] = hw.array_create %[[EQ]], %[[EQ]] a2 = hw.ArrayCreateOp([eq, eq]) # CHECK: %[[COMBINED:.+]] = hw.array_concat %[[A1]], %[[A2]] combined = hw.ArrayConcatOp(a1, a2) mod.out = hw.BitcastOp(dim(SIZE), combined)
def fsm_wrapper_class(fsm_mod, fsm_name, clock, reset=None): """ Generate a wrapper class for the FSM class which contains the clock and reset signals, as well as a `fsm.hw_instance` instaitiation of the FSM. """ class fsm_hw_mod: @generator def construct(ports): in_ports = { port_name: getattr(ports, port_name) for (port_name, _) in fsm_mod._pycde_mod.input_ports } fsm_instance = fsm_mod(**in_ports) # Attach clock and optional reset on the backedges created during # the MachineOp:instatiate call. clock_be = getattr(fsm_instance._instantiation, '_clock_backedge') connect(clock_be, getattr(ports, clock)) if hasattr(fsm_instance._instantiation, '_reset_backedge'): reset_be = getattr(fsm_instance._instantiation, '_reset_backedge') connect(reset_be, getattr(ports, reset)) # Connect outputs for (port_name, _) in fsm_mod._pycde_mod.output_ports: setattr(ports, port_name, getattr(fsm_instance, port_name)) # Inherit in and output ports. We do this outside of the wrapped class # since we cannot do setattr inside the class scope (def-use). for (name, type) in fsm_mod._pycde_mod.input_ports: setattr(fsm_hw_mod, name, Input(type)) for (name, type) in fsm_mod._pycde_mod.output_ports: setattr(fsm_hw_mod, name, Output(type)) # Add clock and additional reset port. setattr(fsm_hw_mod, clock, Input(types.i1)) if reset is not None: setattr(fsm_hw_mod, reset, Input(types.i1)) # The wrapper class now overloads the name of the user-defined FSM. # From this point on, instantiating the user FSM class will actually # instantiate the wrapper HW module class. fsm_hw_mod.__qualname__ = fsm_name fsm_hw_mod.__name__ = fsm_name fsm_hw_mod.__module__ = fsm_name return fsm_hw_mod
class PolynomialSystem: y = Output(types.i32) @generator def construct(ports): i32 = types.i32 x = hw.ConstantOp(i32, 23) poly = PolynomialCompute(Coefficients([62, 42, 6]))("example") connect(poly.x, x) PolynomialCompute(coefficients=Coefficients([62, 42, 6]))("example2", x=poly.y) PolynomialCompute(Coefficients([1, 2, 3, 4, 5]))("example2", x=poly.y) cp = CoolPolynomialCompute([4, 42]) cp.x.connect(23) m = ExternWithParams(8, 3)() m.name = "pexternInst" ports.y = poly.y
class PolynomialCompute: """Module to compute ax^3 + bx^2 + cx + d for design-time coefficients""" # Evaluate polynomial for 'x'. x = Input(types.i32) y = Output(types.int(8 * 4)) def __init__(self, name: str): """coefficients is in 'd' -> 'a' order.""" self.instance_name = name @staticmethod def get_module_name(): return "PolyComputeForCoeff_" + '_'.join( [str(x) for x in coefficients.coeff]) @generator def construct(mod): """Implement this module for input 'x'.""" x = mod.x taps = list() for power, coeff in enumerate(coefficients.coeff): coeffVal = hw.ConstantOp(types.i32, coeff) if power == 0: newPartialSum = coeffVal else: partialSum = taps[-1] if power == 1: currPow = x else: x_power = [x for i in range(power)] currPow = comb.MulOp(*x_power) newPartialSum = comb.AddOp(partialSum, comb.MulOp(coeffVal, currPow)) taps.append(newPartialSum) # Final output mod.y = taps[-1]
def machine_clocked(to_be_wrapped): """ Wrap a class as an FSM machine. An FSM PyCDE module is expected to implement: - A set of input ports: These can be of any type, and are used to drive the FSM. Transitions can be specified either as a tuple of (next_state, condition) or as a single `next_state` string (unconditionally taken). a `condition` function is a function which takes a single input representing the `ports` of a component, similar to the `@generator` decorator used elsewhere in PyCDE. """ states = {} initial_state = None for name, v in attributes_of_type(to_be_wrapped, State).items(): if name in states: raise ValueError("Duplicate state name: {}".format(name)) v.name = name states[name] = v if v.initial: if initial_state is not None: raise ValueError( f"Multiple initial states specified ({name}, {initial_state})." ) initial_state = name if initial_state is None: raise ValueError( "No initial state specified, please create a state with `initial=True`." ) for name, v in attributes_of_type(to_be_wrapped, Input).items(): if v.type.width != 1: raise ValueError( f"Input port {name} has width {v.type.width}. For now, FSMs only support i1 inputs." ) # At this point, the 'states' attribute should be considered an immutable, # ordered list of states. to_be_wrapped.states = states.values() to_be_wrapped._initial_state = initial_state if len(states) == 0: raise ValueError("No States defined") # Add an output port for each state. for state_name, state in states.items(): output_for_state = Output(types.i1) setattr(to_be_wrapped, 'is_' + state_name, output_for_state) state.output = output_for_state # Store requested clock and reset names. to_be_wrapped.clock_name = clock to_be_wrapped.reset_name = reset # Set module creation and generation callbacks. setattr(to_be_wrapped, 'create_cb', create_fsm_machine_op) setattr(to_be_wrapped, 'generator_cb', generate_fsm_machine_op) # Create a dummy Generator function to trigger module generation. # This function doesn't do anything, since all generation logic is embedded # within generate_fsm_machine_op. In the future, we may allow an actual # @generator function specified by the user if they want to do something # specific. setattr(to_be_wrapped, 'dummy_generator_f', generator(lambda x: None)) # Treat the remainder of the class as a module. # Rename the fsm_mod before creating the module to ensure that the wrapped # module will be named as the user specified (that of fsm_mod), and the # FSM itself will have a suffixed name. fsm_name = to_be_wrapped.__name__ to_be_wrapped.__name__ = to_be_wrapped.__name__ + '_impl' to_be_wrapped.__qualname__ = to_be_wrapped.__qualname__ + '_impl' fsm_mod = module(to_be_wrapped) # Next we build the outer wrapper class that contains clock and reset ports. fsm_hw_mod = fsm_wrapper_class(fsm_mod=fsm_mod, fsm_name=fsm_name, clock=clock, reset=reset) return module(fsm_hw_mod)
class Taps: taps = Output(dim(8, 3)) @generator def build(ports): ports.taps = [203, 100, 23]
class CoolPolynomialCompute: x = Input(types.i32) y = Output(types.i32) def __init__(self, coefficients): self.coefficients = coefficients