Exemplo n.º 1
0
    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([('a', dl.DOptional(int))], dl.Void)
        def node_to_investigate(node):
            while True:
                a = node.receive('a')
                if a is not None:
                    STORE.append(a)
                    raise dl.DeltaRuntimeExit

        @dl.Interactive([], 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):
        # We are using a DOptional 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.DOptional(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 -> initialize 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 istantaneously
        # 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))
Exemplo n.º 3
0
def interact(a: int,
             b: dl.DOptional(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}")
Exemplo n.º 4
0
 def migen_body(self, template):
     template.add_pa_in_port('a', dl.DOptional(int))
Exemplo n.º 5
0
    def migen_body(self, template):
        # creation of input/output ports
        angle = template.add_pa_in_port(
            'angle',
            dl.DOptional(dl.DRaw(dl.DUInt(dl.DSize(ANGLE_MEMORY_WIDTH))))
        )

        hal_command = template.add_pa_out_port('hal_command',
                                               dl.DUInt(dl.DSize(32)))
        shot_completed = template.add_pa_out_port('shot_completed',
                                               dl.DBool())
        # set up  internal signals
        _rotation_command = Signal(32)

        self.comb += (
            # declare input/output ports always happy to receive/transmit data
            angle.ready.eq(1)
        )

        # define finite state machine for triggering HAL command sequences
        self.submodules.commander_fsm = \
            commander_fsm = FSM(reset_state="STATE_PREPARATION")

        # waits for angle signal before kicking off HAL sequence
        commander_fsm.act(
            "STATE_PREPARATION",
            NextValue(shot_completed.valid, 0),
            If(
                angle.valid == 1,
                NextValue(
                    _rotation_command,
                    dl.lib.command_creator("RX", argument=angle.data)
                ),
                NextValue(hal_command.valid, 1),
                NextValue(
                    hal_command.data,
                    dl.lib.command_creator("STATE_PREPARATION")
                ),
                NextState("ROTATION")
            ).Else(
                NextValue(hal_command.valid, 0),
                NextState("STATE_PREPARATION")
            )
        )

        # align HAL command to rotation
        commander_fsm.act(
            "ROTATION",
            NextValue(hal_command.valid, 1),
            NextValue(hal_command.data, _rotation_command),
            NextValue(shot_completed.valid, 0),
            NextState("STATE_MEASURE")
        )

        # align HAL command to state measure
        commander_fsm.act(
            "STATE_MEASURE",
            NextValue(hal_command.valid, 1),
            NextValue(hal_command.data,
                      dl.lib.command_creator("STATE_MEASURE")),
            NextValue(shot_completed.valid, 1),
            NextValue(shot_completed.data, 1),
            NextState("STATE_PREPARATION")
        )
Exemplo n.º 6
0
    def migen_body(self, template):
        # creation of input/output ports
        shot_completed = template.add_pa_in_port('shot_completed',
                                              dl.DOptional(dl.DBool()))
        hal_result = template.add_pa_in_port(
            'hal_result',
            dl.DOptional(dl.DUInt(dl.DSize(32)))
        )

        agg_result = template.add_pa_out_port('agg_result',
                                              dl.DInt(dl.DSize(32)))
        # Completed is currently returning a simple 0/1 value but we make space
        # for an error code to be returned e.g. 255, 0b11111111 can be in the
        # future used to represent an error.
        completed = template.add_pa_out_port('completed', dl.DInt(dl.DSize(8)))
        next_angle = template.add_pa_out_port(
            'next_angle',
            dl.DRaw(dl.DUInt(dl.DSize(ANGLE_MEMORY_WIDTH)))
        )

        # generate a ROM of 10-bit angle values
        angles = generate_angles(RESOLUTION)

        self.specials.angle_memory = angle_memory = Memory(
            ANGLE_MEMORY_WIDTH, len(angles), init=angles, name="ANGLE_ROM"
        )
        angle_rom_port = angle_memory.get_port(write_capable=False)
        self.specials += angle_rom_port

        # set up internal signals
        _shots_counter = Signal(32)
        _high_hal_results = Signal(32)
        _reset_high_hal = Signal(1)
        _angle_rom_index = Signal(RESOLUTION+1)

        self.comb += (
            # declare input/output ports always happy to receive/transmit data
            hal_result.ready.eq(1),
            shot_completed.ready.eq(1),

            # align angle ROM address with ROM index signal
            angle_rom_port.adr.eq(_angle_rom_index),
        )

        # define finite state machine for triggering angle and result signals
        self.submodules.rabi_aggregator_fsm = \
            rabi_aggregator_fsm = FSM(reset_state="IDLE")

        # Logic to accumulate measurements
        self.sync += (
            If (_reset_high_hal == 1,
                _high_hal_results.eq(0)
            ).Else (
                If (hal_result.valid == 1,
                    If ((hal_result.data &
                        dl.lib.Masks.MEASUREMENTS.value) == 1,
                        _high_hal_results.eq(_high_hal_results + 1)
                    )
                )
            )
        )

        # waits for the experiment to be kicked off

        rabi_aggregator_fsm.act(
            "IDLE",
            NextValue(agg_result.valid, 0),
            NextValue(next_angle.valid, 0),
            NextValue(completed.valid, 0),
            NextValue(_shots_counter, 0),
            NextValue(_reset_high_hal, 1),
            NextState("DO_SHOTS")
        )

        rabi_aggregator_fsm.act(
            "DO_SHOTS",
            NextValue(agg_result.valid, 0),
            NextValue(_reset_high_hal, 0),
            If (_shots_counter == REPETITIONS,
                NextState("CHECK_IF_COMPLETE"),
                NextValue(_angle_rom_index, _angle_rom_index + 1),
                NextValue(agg_result.data, _high_hal_results),
                NextValue(agg_result.valid, 1),
                NextValue(_reset_high_hal, 1),
             ).Else (
                NextValue(next_angle.data, angle_rom_port.dat_r),
                NextValue(next_angle.valid, 1),
                NextState("WAIT_SHOT")
            )
        )

        rabi_aggregator_fsm.act(
            "WAIT_SHOT",
            NextValue(next_angle.valid, 0),
            If ((shot_completed.valid == 1) & (shot_completed.data == 1),
                NextValue(_shots_counter, _shots_counter + 1),
                NextState("DO_SHOTS"),
            )
        )

        rabi_aggregator_fsm.act(
            "CHECK_IF_COMPLETE",
            NextState("IDLE"),
            NextValue(agg_result.valid, 0),
            If(_angle_rom_index == 2 ** RESOLUTION,
                NextValue(completed.data, 1),
                NextValue(completed.valid, 1),
            )
        )
Exemplo n.º 7
0
 def adder(a: dl.DOptional(int), b: int) -> int:
     if a is None:
         return b
     else:
         return a + b
Exemplo n.º 8
0
    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.DOptional(dl.DInt(dl.DSize(N_BITS * N_INPUTS))))
        self.cmd = template.add_pa_in_port('cmd', dl.DOptional(dl.DInt()))

        # outputs
        self.d_out = template.add_pa_out_port('d_out', dl.DInt())
        self.err = template.add_pa_out_port('error', dl.DInt())

        # 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)
        ]
Exemplo n.º 9
0
 def test_node(a: dl.DOptional(int)) -> dl.Void:
     pass
Exemplo n.º 10
0
 def migen_body(self, template):
     template.add_pa_in_port('i', dl.DOptional(dl.DInt(dl.DSize(8))))
Exemplo n.º 11
0
    def migen_body(self, template):

        _TIME_RES = 32
        # Node inputs
        self.time_in = template.add_pa_in_port('time_in',
                                               dl.DOptional(dl.DUInt()))

        # Node outputs
        self.time_out = template.add_pa_out_port('time_out', dl.DUInt())
        self.counter_reset = template.add_pa_out_port('counter_reset',
                                                      dl.DInt())

        # 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")))
Exemplo n.º 12
0
    def migen_body(self, template):
        # Node Inputs
        # Two inputs, a command and parameters.
        self.DAC_command = template.add_pa_in_port('DAC_command',
                                                   dl.DOptional(dl.DInt()))
        self.DAC_param = template.add_pa_in_port('DAC_param',
                                                 dl.DOptional(dl.DInt()))

        # Node Outputs
        # Returns the node status upon request
        self.DAC_controller_status = template.add_pa_out_port(
            'DAC_controller_status', dl.DInt())
        # Data to be returned to accumulator eg DAC voltage
        self.DAC_return_data = template.add_pa_out_port(
            'DAC_return_data', dl.DInt())

        # Internal signals.
        #####
        # How long to wait for the DAC voltage to settle after a new
        # voltage is set
        self.v_settle_count = Signal(10)
        # Tracks the current status of this node
        self.node_status_internal = Signal(8)
        # 10 bit voltage
        self.DAC_voltage = Signal(10)

        # If self.v_settle_count is not zero it means we've updated the voltage
        # and are waiting for it to settle before initiating more data
        # collection
        self.sync += If(self.v_settle_count != 0,
                        self.v_settle_count.eq(self.v_settle_count - 1))

        # If the DAC_command is a request of node status immediately return
        # present value of node status
        self.comb += If(
            self.DAC_command.data == DAC_STATUS,
            self.DAC_controller_status.data.eq(self.node_status_internal),
            self.DAC_controller_status.valid.eq(1))
        """FSM
        The state machine is used to handle the different commands. These will
        likely involve further communication with the SPI drives to the DAC

        IDLE
        This is the default hold state. It stays here while there is nothing to
        do. When recieving a command the state is then changed appropriately.

        DAC_READ_VOLTAGE
        This state returns the current DAC voltage. This is stored locally in
        DAC_voltage, however, in a real implementation this state would read
        the voltage directly from the DAC IC and return this value.

        DAC_SET_VOLTAGE
        This state sets DAC_voltage equal to the value sent in DAC_param. In a
        real implementation this state would pass the correct set of SPI
        commands to set the DAC voltage.

        SETTLE_HOLD
        This state is used as a hold time after the DAC voltage is written. We
        can assume there is a none zero settle time for the new electrode
        voltage to be set and any oscillations to settle. The time in this hold
        state is set by the self.v_settle_count value, set when moving from
        DAC_SET_VOLTAGE. Once self.cnt reaches 0 then the FSM moves back to
        IDLE. While in this state a poll of the DAC_STATUS will return _BUSY
        """

        self.submodules.DAC_fsm = FSM(reset_state="IDLE")

        self.DAC_fsm.act(
            "IDLE", NextValue(self.DAC_command.ready, 1),
            NextValue(self.DAC_param.ready, 1),
            NextValue(self.DAC_return_data.valid, 0),
            If(self.DAC_command.data == DAC_GET_VOLTAGE,
               NextValue(self.node_status_internal, BUSY),
               NextState("DAC_READ_VOLTAGE")).Elif(
                   self.DAC_command.data == DAC_SET_VOLTAGE,
                   NextValue(self.DAC_voltage, self.DAC_param.data),
                   NextValue(self.node_status_internal, BUSY),
                   NextState("DAC_SET_VOLTAGE")))

        self.DAC_fsm.act(
            "DAC_READ_VOLTAGE",
            NextValue(self.DAC_return_data.data, self.DAC_voltage),
            NextValue(self.DAC_return_data.valid, 1),
            NextValue(self.node_status_internal, READY), NextState("IDLE"))

        self.DAC_fsm.act("DAC_SET_VOLTAGE",
                         NextValue(self.node_status_internal, BUSY),
                         NextValue(self.v_settle_count, 0x1),
                         NextState("SETTLE_HOLD"))

        self.DAC_fsm.act(
            "SETTLE_HOLD",
            If(self.v_settle_count == 0,
               NextValue(self.node_status_internal, READY), NextState("IDLE")))
Exemplo n.º 13
0
import deltalanguage as dl

AccumT, AccumC = dl.make_forked_return({
    'DAC_command': int,
    'DAC_param': int,
    'photon': int,
    'reset': int
})

TIME_RES = 30


@dl.Interactive([('new_time', dl.DUInt()), ('DAC_status', int),
                 ('DAC_voltage', int),
                 ('experiment_start', dl.DOptional(bool))], AccumT)
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
        - DAC_voltage: DAC voltage
        - experiment_start: Trigger from UI