def migen_body(self, template): inp = template.add_pa_in_port('inp', dl.Optional(int)) trigger = template.add_pa_in_port('trigger', dl.Optional(int)) out = template.add_pa_out_port('out', int) # Declare input and output ports always happy to receive/transmit data self.comb += ( inp.ready.eq(1), trigger.ready.eq(1), out.ready.eq(1), ) commander_fsm = migen.FSM(reset_state="IDLE") self.submodules.commander_fsm = commander_fsm commander_fsm.act("IDLE", migen.If(inp.valid == 1, migen.NextState("LOADING"))) commander_fsm.act( "LOADING", migen.If(trigger.valid & trigger.data == 1, migen.NextState("RETURN")).Else( migen.NextValue(out.data, out.data + inp.data), )) commander_fsm.act("RETURN", migen.NextValue(out.valid, 1), migen.NextState("IDLE"))
def migen_body(self, template): # Input/Outputs start here: # 2 inputs and 2 outputs. # # This block group ports which will be accessed by migen, using # the protocol adapters. # in_ports and out_ports implement a similar logic based on 3 # signals, ready, valid and data. # An input can be received when the ready signal is = '1'. # data contains the value of the message that we are receiving # and can considered sensible only when valid = '1', i.e. when # a new data has been received on the pa_input_port. # The opposite logic holds true for the outputs. in1 = template.add_pa_in_port('in1', dl.Optional(int)) in2 = template.add_pa_in_port('in2', dl.Optional(int)) out1 = template.add_pa_out_port('out1', int) out2 = template.add_pa_out_port('out2', int) # The main migen logic starts here: # Everything below is just an example that show different routines. # Add a 32-bit counter (0-2**32-1) which will increment at each clock # cycle. self.counter = migen.Signal(32) self.sync += self.counter.eq(self.counter + 1) # Add a condition when in_ports are ready. self.comb += migen.If( self.counter >= 3, in1.ready.eq(1), in2.ready.eq(1) ) # Pretend that we do a useful calculations. # Here we first check that the outputs are ready. # Then wait for the counter to reach 100. # And write outputs. # Note that the output should be marked as valid. self.comb += migen.If( (out1.ready & out2.ready) == 1, migen.If( self.counter == 5, out1.data.eq(in1.data + in2.data), out2.data.eq(self.counter), out1.valid.eq(in1.valid & in2.valid), out2.valid.eq(in1.valid & in2.valid) ).Else( out1.valid.eq(0), out2.valid.eq(0) ) )
def migen_body(self, template): """ This is the body of the migen node connecting the pulser and timestamper as 2 submodules. """ # Node inputs self.reset = template.add_pa_in_port('reset', dl.Optional(int)) self.photon = template.add_pa_in_port('photon', dl.Optional(int)) # Node outputs self.time = template.add_pa_out_port('time', dl.UInt()) self.error = template.add_pa_out_port('error', dl.Int()) self.rf_trigger = Signal(1) self.pmt_trigger = Signal(1) self.hit_channels = Signal(2) self.clock = Signal(TIME_RES) ### self.comb += [ self.hit_channels.eq(self.pmt_trigger + 2 * self.rf_trigger), self.photon.ready.eq(1), ] # error management ( if photon is outside valid range) self.sync += [ If( self.photon.valid & ((self.photon.data < 1) | (self.photon.data > TIME_RES - 1)), self.error.data.eq(1), self.error.valid.eq(1)).Elif( self.photon.valid, self.error.data.eq(0), self.error.valid.eq(1)).Else( self.error.data.eq(self.error.data), self.error.valid.eq(0)) ] self.pulser_inst = TimestamperModel.Pulser(self.reset, self.pmt_trigger, self.rf_trigger, self.photon, self.clock) self.timestamper_inst = TimestamperModel.Timestamper( self.hit_channels, self.time, self.reset, self.clock) self.submodules += [self.timestamper_inst, self.pulser_inst]
def multi_count_print_exit(self, x: dl.Optional(int), y: dl.Optional(int), z: dl.Optional(bool)): """Count and store messages until we receive self.max_count many, then print all stores and exit. """ if x is not None: self.x_store.append(x) self.curr_count += 1 if y is not None: self.y_store.append(y) self.curr_count += 1 if z is not None: self.z_store.append(z) self.curr_count += 1 if self.curr_count >= self.max_count: print(self.x_store) print(self.y_store) print(self.z_store) raise DeltaRuntimeExit
def migen_body(self, template): start = template.add_pa_in_port('start', dl.Optional(int)) out_a = template.add_pa_out_port('out_a', int) out_b = template.add_pa_out_port('out_b', int) # This will need to be converted to boolean when migen nodes support # boolean self.cnt = migen.Signal(10) self.comb += (out_a.ready.eq(1), out_b.ready.eq(1), start.ready.eq(1)) self.sync += migen.If(self.cnt & 0x1, out_a.valid.eq(start.data), out_b.valid.eq(0)).Else( out_a.valid.eq(0), out_b.valid.eq(start.data)) self.sync += (self.cnt.eq(self.cnt + 1), out_a.data.eq(self.cnt), out_b.data.eq(self.cnt))
def migen_body(self, template): # We are using a Optional here because the code should run # no matter the input. If you were to use an int (so a # not-optional input) we would stall the migen simulation until # an input is received. In this example, we have a cyclical graph # in which the hardware node (migenNode) must produce an output # (a reset signal) no matter the input. pulse_in = template.add_pa_in_port('pulse_in', dl.Optional(int)) reset_out = template.add_pa_out_port('reset_out', int) # Constants self.NUM_CLOCKS = 5 # We set the lowest NUM_CLOCKS bits of INIT_VAL to be '1's self.INIT_VAL = 2**self.NUM_CLOCKS-1 # Signal that generates a pulse of length NUM_CLOCKS self.shaper = migen.Signal(self.NUM_CLOCKS+1) # When I receive a reset signal -> initialise the shaper to contain # N '1's. # If I haven't received one just shift the value to the left # 01111 -> 00111. I will use the lowest bit for the reset_out signal # This equates to seconding N times a '1' after receiving a pulse_in # followed by '0'. Note: the sync construct instructs migen that the # logic contained within the block is sequential - i.e. it can only # change on a clock transaction (from low to high). self.sync += ( migen.If( (pulse_in.valid == 1) & (pulse_in.data == 1), self.shaper.eq(self.INIT_VAL) ).Else( self.shaper.eq(self.shaper >> 1) ) ) # Always generating an output self.sync += (reset_out.data.eq(self.shaper[0])) # Always ready to receive a reset, always generating an output. # Note: comb (combinatorial logic) is executed instantaneously # when inputs change. In this example, inputs for the # reset_out.valid is a constant 1 so it is always = 1. # If it was a signal the value of reset_out.valid would change # together with the input signal. self.comb += (reset_out.valid.eq(1), pulse_in.ready.eq(1), reset_out.ready.eq(1))
def test_interactive_node_with_only_optional_input(self): """Test that an interactive node with an only optional input and no outputs or outputs that can be reached only in a special case wouldn't block the simulation. This is implemented via addition of a 1 ns wait when a node attempts to receive an input and it's trivial. Note that non-interactive nodes with such behaviour won't block as they always call ``send``. """ @dl.Interactive(inputs=[('a', dl.Optional(int))]) def node_to_investigate(node): while True: a = node.receive('a') if a is not None: STORE.append(a) raise dl.DeltaRuntimeExit @dl.Interactive(outputs=[('output', int)]) def rest_of_graph(node): """This node imitates the rest of the graph that does some computations and occasionally provides an input.""" i = 0 while True: i += 1 a = 40 b = 2 a, b = b, a if i % 1000 == 0: node.send(10) else: node.send(None) with dl.DeltaGraph() as graph: opt_input = rest_of_graph.call() node_to_investigate.call(a=opt_input) start_time = time.time() dl.DeltaPySimulator(graph).run() total_time = time.time() - start_time self.assertEqual(STORE, [10]) self.assertLessEqual(total_time, 1)
def migen_body(self, template): # I/O: i1 = template.add_pa_in_port("i1", dl.Optional(int)) o1 = template.add_pa_out_port("o1", int) # LOGIC: self.counter = migen.Signal(1000) self.sync += self.counter.eq(self.counter + 1) # Two memories for testing self.specials.mem1 = mem1 = migen.Memory(32, 100, init=[5, 15, 32]) read_port1 = mem1.get_port() self.specials.mem2 = mem2 = migen.Memory(32, 100, init=[2, 4, 6, 8]) read_port2 = mem2.get_port() self.specials += read_port1 self.specials += read_port2 self.mem_out1 = migen.Signal(32) self.mem_out2 = migen.Signal(32) self.sync += (read_port1.adr.eq(self.counter), self.mem_out1.eq(read_port1.dat_r)) self.sync += (read_port2.adr.eq(self.counter), self.mem_out2.eq(read_port2.dat_r)) # DEBUGGING: # add any signal you want to see in debugging and printing format # (in_ports, out_ports, inputs, output are added by default): self.debug_signals = {'counter': (self.counter, '05b')} self.comb += migen.If( self.counter >= 5, i1.ready.eq(1) ) self.comb += migen.If( o1.ready == 1, migen.If( self.counter == 10, o1.data.eq(self.counter+i1.data), o1.valid.eq(1) ).Else( o1.valid.eq(0) ) )
def interact(a: int, b: dl.Optional(int) = None, node: dl.PythonNode = None) -> int: """Node that will set up an interactive console. The user will be able to see local variables, arguments and globals. Through the node object, they can also send & receive messages. Args: a : int An argument the user can access (either 1 or 2 to distinguish the two nodes) b : int An argument sent from one node to the other """ c = 9 d = 10 if b == -1: raise dl.DeltaRuntimeExit code.interact(banner=f"{a}", local=dict(globals(), **locals()), exitmsg=f"Exiting {a}")
def migen_body(self, template): # I/O: i1 = template.add_pa_in_port("i1", dl.Optional(int)) o1 = template.add_pa_out_port("o1", int) self.comb += ( i1.ready.eq(1), ) started = migen.Signal(1) self.sync += migen.If( i1.valid == 1, o1.valid.eq(1), o1.data.eq(i1.data+1) ).Else( o1.data.eq(0), migen.If(started == 0, o1.valid.eq(1), started.eq(1) ).Else( o1.valid.eq(0) ) )
n1 = node.receive('n1') n2 = node.receive('n2') node.send(n1 + n2) @dl.Interactive([('n1', int), ('n2', int)], [('output', int)]) def add_interactive_loop_staggerd(node): while True: n1 = node.receive('n1') n2 = node.receive('n2') n1 += node.receive('n1') n2 += node.receive('n2') node.send(n1 + n2) @dl.Interactive([('n1', dl.Optional(int)), ('n2', int)], [('output', int)]) def add_interactive_loop_optional(node): while True: n1 = node.receive('n1') n2 = node.receive('n2') if n1: node.send(n1 + n2) else: node.send(n2) # Increment: 1 input, 1 output @dl.DeltaBlock(allow_const=True) def increment_const(val: int) -> int:
def migen_body(self, template): template.add_pa_in_port('i', dl.Optional(dl.Int(dl.Size(8))))
def migen_body(self, template): _TIME_RES = 32 # Node inputs self.time_in = template.add_pa_in_port('time_in', dl.Optional(dl.UInt())) # Node outputs self.time_out = template.add_pa_out_port('time_out', dl.UInt()) self.counter_reset = template.add_pa_out_port('counter_reset', dl.Int()) # Internal signals self.pmt_reg = Signal(_TIME_RES) self.rf_reg = Signal(_TIME_RES) self.pmt_trig = Signal(1) self.rf_trig = Signal(1) self.submodules.fsm = FSM(reset_state="RESET_COUNTER") self.sync += [ If( self.pmt_trig, self.pmt_reg.eq(self.time_in.data), ).Elif(self.fsm.ongoing("RESET_COUNTER"), self.pmt_reg.eq(0)).Else(self.pmt_reg.eq(self.pmt_reg)), If( self.rf_trig, self.rf_reg.eq(self.time_in.data), ).Elif(self.fsm.ongoing("RESET_COUNTER"), self.rf_reg.eq(0)).Else(self.rf_reg.eq(self.rf_reg)) ] """FSM The FSM is used to control the readouts from the HPTDC chip and generate a time signal for the accumulator RESET_COUNTER This is the dinitial state of the FSM at the start of the experiment. It resets the "coarse counter" of the HPTDC chip to establish a TO time reference. WAIT_FOR_PMT This state holds until the PMT timestamp is available at the HPTDC chip readout (first data_ready sync pulse) WAIT_FOR_RF This state holds until the RMT timestamp is available at the HPTDC chip readout (second data_ready sync pulse) SEND_TIME In this state, the difference between t_PMT and t_RF is derived and sent to the accumulator. WAIT_ACC_LATENCY This state is used to wait for any delays on inter-node communication """ self.fsm.act( "RESET_COUNTER", self.pmt_trig.eq(0), self.rf_trig.eq(0), self.time_in.ready.eq(1), self.counter_reset.data.eq(1), # reset counters self.counter_reset.valid.eq(1), NextState("WAIT_FOR_PMT")) self.fsm.act( "WAIT_FOR_PMT", self.counter_reset.data.eq(0), self.time_in.ready.eq(1), If(self.time_in.valid, self.pmt_trig.eq(1), NextState("WAIT_FOR_RF"))) self.fsm.act( "WAIT_FOR_RF", self.time_in.ready.eq(1), If(self.time_in.valid, self.rf_trig.eq(1), NextState("SEND_TIME"))) self.fsm.act("SEND_TIME", self.time_in.ready.eq(1), self.time_out.data.eq(self.rf_reg - self.pmt_reg), self.time_out.valid.eq(1), NextState("WAIT_ACC_LATENCY")) self.fsm.act("WAIT_ACC_LATENCY", If(self.time_in.valid == 0, NextState("RESET_COUNTER")))
def test_node(a: dl.Optional(int)) -> dl.Void: pass
def add_optional(n1: dl.Optional(int), n2: int) -> int: if n1: return n1 + n2 else: return n2
def migen_body(self, template): template.add_pa_in_port('a', dl.Optional(int)) template.add_pa_out_port('x', int) template.add_pa_out_port('y', int)
def migen_body(self, template): # generics N_BITS = template.generics["N_BITS"] # 1-64 N_INPUTS = template.generics["N_INPUTS"] TREE_DEPTH = int(ceil(log2(N_INPUTS))) # inputs self.d_in = template.add_pa_in_port( 'd_in', dl.Optional(dl.Int(dl.Size(N_BITS * N_INPUTS)))) self.cmd = template.add_pa_in_port('cmd', dl.Optional(dl.Int())) # outputs self.d_out = template.add_pa_out_port('d_out', dl.Int()) self.err = template.add_pa_out_port('error', dl.Int()) # input length correction [need a power of 2 sized tree] N_INPUTS_CORR = pow(2, TREE_DEPTH) # internals # correct the size of the input tree to be a power of 2 # and register the inputs self.d_in_full_reg = Signal(N_INPUTS_CORR * N_BITS) self.d_in_valid_reg = Signal(1) self.cmd_data_reg = Signal(8) self.cmd_valid_reg = Signal(1) # register outputs self.d_out_data_reg = Signal(N_BITS + TREE_DEPTH) self.d_out_valid_reg = Signal(1) self.err_data_reg = Signal(1) self.err_valid_reg = Signal(1) # create the 2D array of data [INPUTS x TREE_DEPTH] to route # all the core units in an iterative way. The number of bits is incremented # at each stage to account for the carry in additions. self.d_pipe = Array( Array(Signal(N_BITS + b) for a in range(N_INPUTS_CORR)) for b in range(TREE_DEPTH + 1)) # create the 2D array of error signals. self.e_pipe = Array( Array(Signal(N_BITS) for a in range(N_INPUTS_CORR)) for b in range(TREE_DEPTH)) ### # correct input vector length to match a power of 2. # fill non-provided inputs with 0's (affects mean and minimum) self.sync += [ self.d_in_full_reg.eq(self.d_in.data), self.d_in_valid_reg.eq(self.d_in.valid), self.cmd_data_reg.eq(self.cmd.data), self.cmd_valid_reg.eq(self.cmd.valid) ] # wiring inputs to the first stage of the tree for i in range(N_INPUTS_CORR): self.comb += [ self.d_pipe[0][i].eq(self.d_in_full_reg[N_BITS * i:N_BITS * (i + 1)]) ] # instantiation of the core units. for j in range(TREE_DEPTH): for i in range(int(N_INPUTS_CORR / (pow(2, j + 1)))): self.submodules += CoreUnit(self.d_pipe[j][2 * i], self.d_pipe[j][2 * i + 1], self.d_pipe[j + 1][i], self.cmd_data_reg, self.e_pipe[j][i], N_BITS) # error signal propagation. If any of the single units have # a high error signal, the error is propagated to the node's output. self.comb += [ If(self.e_pipe[j][i] == 1, self.err_data_reg.eq(1)) ] self.comb += [ self.d_in.ready.eq(1), self.cmd.ready.eq(1), self.d_out_data_reg.eq(self.d_pipe[TREE_DEPTH][0]), If(self.d_in_valid_reg, self.err_valid_reg.eq(1), self.d_out_valid_reg.eq(1)).Else(self.err_valid_reg.eq(0)) ] self.sync += [ self.d_out.data.eq(self.d_out_data_reg), self.d_out.valid.eq(self.d_out_valid_reg), self.err.data.eq(self.err_data_reg), self.err.valid.eq(self.err_valid_reg) ]
def migen_body(self, template): template.add_pa_in_port('in1', dl.Optional(int))
from matplotlib import ticker from progress.bar import Bar import time import math import random import numpy as np import logging import deltalanguage as dl TIME_RES = 30 @dl.Interactive(inputs=[('new_time', dl.UInt()), ('DAC_status', int), ('DAC_voltage', int), ('experiment_start', dl.Optional(bool))], outputs=[('DAC_command', int), ('DAC_param', int), ('photon', int), ('reset', int)]) def accumulator(node): """ Accumulator Node This node collects times sent from the counter FPGA node and issues commands to the DAC controller. This allows the node to collect data, fit to the data and feedback to the experiment. The process can loop until some minimum threshold is reached, allowing full automation of micromotion compensation. Inputs: - new_time: Time from Counter node - DAC_status: DAC status from DAC node
def mix_optional(n1: dl.Optional(int), n2: int) -> int: if n1: return n1 + n2, n1 * n2 else: return n2, n2
sys_clk += 1 if sys_clk == 100: node.send(pmt_trigger=False, rf_trigger=True) # The nodes are asynchronous, time is random even if fixed here photon = random.randrange(10, 99) sys_clk = 0 elif photon == sys_clk: node.send(pmt_trigger=True, rf_trigger=False) else: node.send(pmt_trigger=False, rf_trigger=False) @dl.Interactive([('pmt', dl.Optional(bool)), ('rf', dl.Optional(bool))], int) def counter(node): """ counter The counter node receives the 2 trigger pulses from hardware and counts the time between the pulses. The unit of time in a real system will be clock ticks from the fastest clock available! """ while True: if node.receive('pmt') is True: start = time.time() # record the time when the photon arrived while node.receive('rf') is not True: # Hold until RF trigger pass # Calculate time between triggers cnt = (time.time() - start)