def experiment_stopper(completed: dl.DInt(dl.DSize(8))) -> dl.Void: if completed: if completed == 1: raise dl.DeltaRuntimeExit else: print(f"The experiment returned error code: {completed}") raise RuntimeError("Experiment returned an error", completed)
def testbench(node): data_array = generate_data_vector(C_N_BITS, C_N_INPUTS) # Temporary - needs df.DArray => migen.Array support data_vector = 0 logging.debug(f'data sent to DUT {data_array}') for i in range(C_N_INPUTS): data_vector += data_array[i] << C_N_BITS * i data_vector = dl.DInt( dl.DSize(C_VECTOR_LEN)).from_numpy_object(data_vector) for cmd in range(0x01, 0x06): node.send(TbVals(data=data_vector, cmd=cmd)) result = node.receive('result') error = node.receive('error') logging.debug(f'cmd: {cmd}') exp_err = 0 if cmd == Commands.MIN: exp_res = np.min(data_array) logging.debug(f'result: {result}, expected: {exp_res}') assert result == exp_res elif cmd == Commands.MAX: exp_res = np.max(data_array) logging.debug(f'result: {result}, expected: {exp_res}') assert result == exp_res elif cmd == Commands.SUM: exp_res = np.sum(data_array) logging.debug(f'result: {result}, expected: {exp_res}') assert result == exp_res elif cmd == Commands.AVG: exp_res_low = trunc(np.mean(data_array)) - 1 exp_res_high = int(np.mean(data_array)) + 1 exp_res = np.mean(data_array) logging.debug(f'result: {result}, expected: {exp_res}') assert result >= exp_res_low assert result <= exp_res_high else: exp_err = 1 result = -1 exp_res = -1 assert error == exp_err raise dl.DeltaRuntimeExit
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), ) )
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) ]
def experiment_stopper(completed: dl.DInt(dl.DSize(8))) -> dl.Void: raise dl.DeltaRuntimeExit
] 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 generate_data_vector(N_BITS, N_INPUTS): return np.random.randint(0, pow(2, N_BITS), size=N_INPUTS) TbT, TbVals = dl.make_forked_return({ 'cmd': dl.DInt(), 'data': dl.DInt(dl.DSize(C_VECTOR_LEN)) }) @dl.Interactive([('result', dl.DInt()), ('error', dl.DInt())], TbT) def testbench(node): data_array = generate_data_vector(C_N_BITS, C_N_INPUTS) # Temporary - needs df.DArray => migen.Array support data_vector = 0 logging.debug(f'data sent to DUT {data_array}') for i in range(C_N_INPUTS): data_vector += data_array[i] << C_N_BITS * i data_vector = dl.DInt(
def method_func_no_output(self, i: dl.DInt(dl.DSize(8))) -> dl.Void: print(i + 1)
def migen_body(self, template): template.add_pa_in_port('i', dl.DOptional(dl.DInt(dl.DSize(8))))
def forked_return_output(x: dl.DInt(dl.DSize(8)), y: dl.DInt(dl.DSize(8))) -> ForkedReturnT: return ForkedReturn(a=0, b=1, c=1, d=0)
def multi_body_no_output(i: dl.DInt(dl.DSize(8))) -> dl.Void: print(i)
class Foo(): def __init__(self): pass @dl.DeltaMethodBlock() def method_func_no_output(self, i: dl.DInt(dl.DSize(8))) -> dl.Void: print(i + 1) class MigenFoo(dl.MigenNodeTemplate): def migen_body(self, template): template.add_pa_in_port('i', dl.DOptional(dl.DInt(dl.DSize(8)))) @dl.Interactive([('i', dl.DInt(dl.DSize(8)))], outputs=dl.Void) def interactive_func_no_output(node: dl.RealNode): a = node.receive('i') template_no_output_no_body = dl.NodeTemplate(name="template_no_output_no_body", inputs=[('i', dl.DInt(dl.DSize(8)))], outputs=dl.Void) @dl.DeltaBlock() def experiment_stopper(completed: dl.DInt(dl.DSize(8))) -> dl.Void: raise dl.DeltaRuntimeExit
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")))
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"))) @dl.Interactive([('time_out', dl.DUInt()), ('reset', dl.DInt())], dl.DUInt()) def testbench(node): """ Testbench for Timestamper interface node. Starts with random testing and ends with corner cases """ _ITER = 10 for i in range(_ITER): logging.debug(f'---Testbench iter {i}---') time_pmt = random.randint(0, 100) time_rf = random.randint(0, 100) do_test(node, time_pmt, time_rf) raise dl.DeltaRuntimeExit def do_test(node, time_pmt, time_rf):
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")))
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"))) TbT, TbC = dl.make_forked_return({'cmd': dl.DInt(), 'param': dl.DInt()}) @dl.Interactive([('dac_status', dl.DInt()), ('dac_voltage', dl.DInt())], TbT) def testbench(node): """ Testbench for the DAC controller node, coverage of all commands and parameters, out of range values, assertion of expected results """ def do_send_cmd(node, cmd, param): # check if the module is READY if cmd != DAC_STATUS: while True: node.send(TbC(cmd=DAC_STATUS, param=None)) status = node.receive('dac_status') if status == READY: break