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 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 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 FSM: a = Input(types.i1) b = Input(types.i1) c = Input(types.i1) # States A = fsm.State(initial=True) (B, C) = fsm.States(2) # Transitions A.set_transitions((B, lambda ports: ports.a)) B.set_transitions((A, lambda ports: ports.b), (C, )) C.set_transitions((B, lambda ports: ports.a))
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 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 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 ClkError: a = Input(types.i32) @generator def build(ports): # CHECK: ValueError: If 'clk' not specified, must be in clock block ports.a.reg()
class Test: clk = Clock() x = Input(types.i32) @generator def build(ports): ports.x.reg(appid=AppID("reg", 5))
class Top: clk = Input(types.i1) @generator def construct(ports): p = Producer(clk=ports.clk) Consumer(clk=ports.clk, int_in=p.const_out)
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 Consumer: clk = Input(types.i1) int_in = InputChannel(types.i32) @generator def construct(ports): data, valid = ports.int_in.unwrap(ready=1)
class M1: in1 = Input(dim(types.i32, 10)) @generator def build(ports): # CHECK: ValueError: Must specify either shape and dtype, or initialize from a value, but not both. NDArray((10, 32), from_value=ports.in1, dtype=types.i1, name='m1')
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 Producer: clk = Input(types.i1) const_out = OutputChannel(types.i32) @generator def construct(ports): chan, ready = types.channel(types.i32).wrap(42, valid=1) ports.const_out = chan
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 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(types.i31) @generator def build(ports): m = NDArray((32, 32), dtype=types.i1, name='m1') # CHECK: ValueError: Width mismatch between provided BitVectorValue (i31) and target shape ([32]i1). m[0] = ports.in1
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 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 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 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 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 M1: in1 = Input(types.i32) @generator def build(ports): m = NDArray((10), dtype=types.i32, name='m1') for i in range(9): m[i] = ports.in1 # CHECK: ValueError: Unassigned sub-matrices: # CHECK: {{[[]}}{{[[]}}9{{[]]}}{{[]]}} m.to_circt()
class F0: a = Input(types.i1) b = Input(types.i1) c = Input(types.i1) def maj3(ports): def nand(*args): return comb.XorOp(comb.AndOp(*args), types.i1(1)) c1 = nand(ports.a, ports.b) c2 = nand(ports.b, ports.c) c3 = nand(ports.a, ports.c) return nand(c1, c2, c3) idle = fsm.State(initial=True) (A, B, C) = fsm.States(3) idle.set_transitions((A, )) A.set_transitions((B, lambda ports: ports.a)) B.set_transitions((C, maj3)) C.set_transitions((idle, lambda ports: ports.c), (A, lambda ports: comb.XorOp(ports.b, types.i1(1))))