LArPix Control Core

A module to control the LArPix chip.

class larpix.larpix.Chip(chip_id, chip_key)[source]

Represents one LArPix chip and helps with configuration and packet generation.

get_configuration_packets(packet_type, registers=None)[source]

Return a list of Packet objects to read or write (depending on packet_type) the specified configuration registers (or all registers by default).

sync_configuration(index=-1)[source]

Adjust self.config to match whatever config read packets are in self.reads[index].

Defaults to the most recently read PacketCollection. Later packets in the list will overwrite earlier packets. The index parameter could be a slice.

export_reads(only_new_reads=True)[source]

Return a dict of the packets this Chip has received.

If only_new_reads is True (default), then only the packets since the last time this method was called will be in the dict. Otherwise, all of the packets stored in self.reads will be in the dict.

class larpix.larpix.Configuration[source]

Represents the desired configuration state of a LArPix chip.

register_names = ['pixel_trim_thresholds', 'global_threshold', 'csa_gain', 'csa_bypass', 'internal_bypass', 'csa_bypass_select', 'csa_monitor_select', 'csa_testpulse_enable', 'csa_testpulse_dac_amplitude', 'test_mode', 'cross_trigger_mode', 'periodic_reset', 'fifo_diagnostic', 'sample_cycles', 'test_burst_length', 'adc_burst_length', 'channel_mask', 'external_trigger_mask', 'reset_cycles']

This attribute lists the names of all available configuration registers. Each register name is available as its own attribute for inspecting and setting the value of the corresponding register.

Certain configuration values are set channel-by-channel. These are represented by a list of values. For example:

>>> conf.pixel_trim_thresholds[2:5]
[16, 16, 16]
>>> conf.channel_mask[20] = 1
>>> conf.external_trigger_mask = [0] * 32

Additionally, other configuration values take up more than or less than one complete register. These are still set by referencing the appropriate name. For example, cross_trigger_mode shares a register with a few other values, and adjusting the value of the cross_trigger_mode attribute will leave the other values unchanged.

compare(config)[source]

Returns a dict containing pairs of each differently valued register Pair order is (self, other)

get_nondefault_registers()[source]

Return a dict of all registers that are not set to the default configuration (i.e. of the ASIC on power-up). The keys are the register name where there’s a difference, and the values are tuples of (current, default) configuration values.

enable_channels(list_of_channels=None)[source]

Shortcut for changing the channel mask for the given channels to “enable” (i.e. 0).

disable_channels(list_of_channels=None)[source]

Shortcut for changing the channel mask for the given channels to “disable” (i.e. 1).

enable_external_trigger(list_of_channels=None)[source]

Shortcut for enabling the external trigger functionality for the given channels. (I.e. disabling the mask.)

disable_external_trigger(list_of_channels=None)[source]

Shortcut for disabling the external trigger functionality for the given channels. (I.e. enabling the mask.)

enable_testpulse(list_of_channels=None)[source]

Shortcut for enabling the test pulser for the given channels.

disable_testpulse(list_of_channels=None)[source]

Shortcut for disabling the test pulser for the given channels.

enable_analog_monitor(channel)[source]

Shortcut for enabling the analog monitor on the given channel.

disable_analog_monitor()[source]

Shortcut for disabling the analog monitor (on all channels).

to_dict()[source]

Export the configuration register names and values into a dict.

from_dict(d)[source]

Use a dict of {register_name, value} to update the current configuration. Not all registers must be in the dict - only those present will be updated.

from_dict_registers(d)[source]

Load in the configuration specified by a dict of (register, value) pairs.

write(filename, force=False, append=False)[source]

Save the configuration to a JSON file.

load(filename)[source]

Load a JSON file and use the contents to update the current configuration.

class larpix.larpix.Controller[source]

Controls a collection of LArPix Chip objects.

Reading data:

The specific interface for reading data is selected by specifying the io attribute. These objects all have similar behavior for reading in new data. On initialization, the object will discard any LArPix packets sent from ASICs. To begin saving incoming packets, call start_listening(). Data will then build up in some form of internal register or queue. The queue can be emptied with a call to read(), which empties the queue and returns a list of Packet objects that were in the queue. The io object will still be listening for new packets during and after this process. If the queue/register fills up, data may be discarded/lost. To stop saving incoming packets and retrieve any packets still in the queue, call stop_listening(). While the Controller is listening, packets can be sent using the appropriate methods without interrupting the incoming data stream.

Properties and attributes:

  • chips: the Chip objects that the controller controls
  • all_chips: all possible Chip objects (considering there are a finite number of chip IDs), initialized on object construction
  • reads: list of all the PacketCollections that have been sent back to this controller. PacketCollections are created by run, write_configuration, read_configuration, multi_write_configuration, multi_read_configuration, and store_packets.
  • use_all_chips: if True, look up chip objects in self.all_chips, else look up in self.chips (default: False)
get_chip(chip_key)[source]

Retrieve the Chip object that this Controller associates with the given chip_key.

add_chip(chip_key, chip_id=0, safe=True)[source]

Add a specified chip to the Controller chips

param: chip_key: chip key to specify unique chip param: chip_id: chip id to associate with chip param: safe: check if chip key is valid using current io

load(filename, safe=True)[source]

Loads the specified file that describes the chip ids and IO network

Parameters:
  • filename – File path to configuration file
  • safe – Flag to check chip keys against current io
load_controller(filename, safe=True)[source]

Loads the specified file using the basic key, chip format The key, chip file format is: `` {

“name”: “<system name>”, “chip_list”: [[<chip key>, <chip id>],…]

The chip key is the Controller access key that gets communicated to/from the io object when sending and receiving packets.

Parameters:
  • filename – File path to configuration file
  • safe – Flag to check chip keys against current io
load_daisy_chain(filename, safe=True)[source]

Loads the specified file in a basic daisy chain format Daisy chain file format is: `` {

“name”: “<board name>”, “chip_list”: [[<chip id>,<daisy chain>],…]

Position in daisy chain is specified by position in chip_set list returns board name of the loaded chipset configuration

Parameters:
  • filename – File path to configuration file
  • safe – Flag to check chip keys against current io
send(packets)[source]

Send the specified packets to the LArPix ASICs.

start_listening()[source]

Listen for packets to arrive.

stop_listening()[source]

Stop listening for new packets to arrive.

read()[source]

Read any packets that have arrived and return (packets, bytestream) where bytestream is the bytes that were received.

The returned list will contain packets that arrived since the last call to read or start_listening, whichever was most recent.

write_configuration(chip_key, registers=None, write_read=0, message=None)[source]

Send the configurations stored in chip.config to the LArPix ASIC.

By default, sends all registers. If registers is an int, then only that register is sent. If registers is an iterable, then all of the registers in the iterable are sent.

If write_read == 0 (default), the configurations will be sent and the current listening state will not be affected. If the controller is currently listening, then the listening state will not change and the value of write_read will be ignored. If write_read > 0 and the controller is not currently listening, then the controller will listen for write_read seconds beginning immediately before the packets are sent out, read the io queue, and save the packets into the reads data member. Note that the controller will only read the queue once, so if a lot of data is expected, you should handle the reads manually and set write_read to 0 (default).

read_configuration(chip_key, registers=None, timeout=1, message=None)[source]

Send “configuration read” requests to the LArPix ASIC.

By default, request all registers. If registers is an int, then only that register is reqeusted. If registers is an iterable, then all of the registers in the iterable are requested.

If the controller is currently listening, then the requests will be sent and no change to the listening state will occur. (The value of timeout will be ignored.) If the controller is not currently listening, then the controller will listen for timeout seconds beginning immediately before the first packet is sent out, and will save any received packets in the reads data member.

multi_write_configuration(chip_reg_pairs, write_read=0, message=None)[source]

Send multiple write configuration commands at once.

chip_reg_pairs should be a list/iterable whose elements are an valid arguments to Controller.write_configuration, excluding the write_read argument. Just like in the single Controller.write_configuration, setting write_read > 0 will have the controller read data during and after it writes, for however many seconds are specified.

Examples:

These first 2 are equivalent and write the full configurations

>>> controller.multi_write_configuration([chip_key1, chip_key2, ...])
>>> controller.multi_write_configuration([(chip_key1, None), chip_key2, ...])

These 2 write the specified registers for the specified chips in the specified order

>>> controller.multi_write_configuration([(chip_key1, 1), (chip_key2, 2), ...])
>>> controller.multi_write_configuration([(chip_key1, range(10)), chip_key2, ...])
multi_read_configuration(chip_reg_pairs, timeout=1, message=None)[source]

Send multiple read configuration commands at once.

chip_reg_pairs should be a list/iterable whose elements are chip keys (to read entire configuration) or (chip_key, registers) tuples to read only the specified register(s). Registers could be None (i.e. all), an int for that register only, or an iterable of ints.

Examples:

These first 2 are equivalent and read the full configurations

>>> controller.multi_read_configuration([chip_key1, chip_key2, ...])
>>> controller.multi_read_configuration([(chip_key1, None), chip_key2, ...])

These 2 read the specified registers for the specified chips in the specified order

>>> controller.multi_read_configuration([(chip_key1, 1), (chip_key2, 2), ...])
>>> controller.multi_read_configuration([(chip_key1, range(10)), chip_key2, ...])
run(timelimit, message)[source]

Read data from the LArPix ASICs for the given timelimit and associate the received Packets with the given message.

verify_configuration(chip_keys=None, timeout=0.1)[source]

Read chip configuration from specified chip(s) and return True if the read chip configuration matches the current configuration stored in chip instance. chip_keys can be a single chip key, a list of chip keys, or None. If chip_keys is None all chips will be verified.

Also returns a dict containing the values of registers that are different (read register, stored register)

read_channel_pedestal(chip_key, channel, run_time=0.1)[source]

Set channel threshold to 0 and report back on the recieved adcs from channel Returns mean, rms, and packet collection

enable_analog_monitor(chip_key, channel)[source]

Enable the analog monitor on a single channel on the specified chip. Note: If monitoring a different chip, call disable_analog_monitor first to ensure that the monitor to that chip is disconnected.

disable_analog_monitor(chip_key=None, channel=None)[source]

Disable the analog monitor for a specified chip and channel, if none are specified disable the analog monitor for all chips in self.chips and all channels

enable_testpulse(chip_key, channel_list, start_dac=255)[source]

Prepare chip for pulsing - enable testpulser and set a starting dac value for specified chip/channel

issue_testpulse(chip_key, pulse_dac, min_dac=0)[source]

Reduce the testpulser dac by pulse_dac and write_read to chip for 0.1s

disable_testpulse(chip_key=None, channel_list=range(0, 32))[source]

Disable testpulser for specified chip/channels. If none specified, disable for all chips/channels

disable(chip_key=None, channel_list=range(0, 32))[source]

Update channel mask to disable specified chips/channels. If none specified, disable all chips/channels

enable(chip_key=None, channel_list=range(0, 32))[source]

Update channel mask to enable specified chips/channels. If none specified, enable all chips/channels

store_packets(packets, data, message)[source]

Store the packets in self and in self.chips

sort_packets(collection)[source]

Sort the packets in collection into each chip in self.all_chips (if self.use_all_chips) or self.chips (otherwise).

save_output(filename, message)[source]

Save the data read by each chip to the specified file.

class larpix.larpix.Packet(bytestream=None)[source]

A single 54-bit LArPix UART data packet.

LArPix Packet objects have attributes for inspecting and modifying the contents of the packet.

Internally, packets are represented as an array of bits, and the different attributes use Python “properties” to seamlessly convert between the bits representation and a more intuitive integer representation. The bits representation can be inspected with the bits attribute.

Packet objects do not restrict you from adjusting an attribute for an inappropriate packet type. For example, you can create a data packet and then set packet.register_address = 5. This will adjust the packet bits corresponding to a configuration packet’s “register_address” region, which is probably not what you want for your data packet.

Packets have a parity bit which enforces odd parity, i.e. the sum of all the individual bits in a packet must be an odd number. The parity bit can be accessed as above using the parity_bit_value attribute. The correct parity bit can be computed using compute_parity(), and the validity of a packet’s parity can be checked using has_valid_parity(). When constructing a new packet, the correct parity bit can be assigned using assign_parity().

Individual packets can be printed to show a human-readable interpretation of the packet contents. The printed version adjusts its output based on the packet type, so a data packet will show the data word, timestamp, etc., while a configuration packet will show the register address and register data.

bytes()[source]

Construct the bytes that make up the packet.

Byte 0 is the first byte that would be sent out and contains the first 8 bits of the packet (i.e. packet type and part of the chip ID).

Note: The internal bits representation of the packet has a different endian-ness compared to the output of this method.

export()[source]

Return a dict representation of this Packet.

class larpix.larpix.PacketCollection(packets, bytestream=None, message='', read_id=None, skipped=None)[source]

Represents a group of packets that were sent to or received from LArPix.

Index into the PacketCollection as if it were a list:

>>> collection[0]
Packet(b'')
>>> first_ten = collection[:10]
>>> len(first_ten)
10
>>> type(first_ten)
larpix.larpix.PacketCollection
>>> first_ten.message
'my packets | subset slice(None, 10, None)'

To view the bits representation, add ‘bits’ to the index:

>>> collection[0, 'bits']
'00000000 00000000 00000000 00000000 00000000 00000000 000111'
>>> bits_format_first_10 = collection[:10, 'bits']
>>> type(bits_format_first_10[0])
str
to_dict()[source]

Export the information in this PacketCollection to a dict.

from_dict(d)[source]

Load the information in the dict into this PacketCollection.

extract(attr, **selection)[source]

Extract the given attribute from packets specified by selection and return a list.

Any key used in Packet.export is a valid attribute or selection:

  • all packets:
    • chip_key
    • bits
    • type (data, test, config read, config write)
    • chipid
    • parity
    • valid_parity
  • data packets:
    • channel
    • timestamp
    • adc_counts
    • fifo_half
    • fifo_full
  • test packets:
    • counter
  • config packets:
    • register
    • value

Usage:

>>> # Return a list of adc counts from any data packets
>>> adc_data = collection.extract('adc_counts')
>>> # Return a list of timestamps from chip 2 data
>>> timestamps = collection.extract('timestamp', chipid=2)
>>> # Return the most recently read global threshold from chip 5
>>> threshold = collection.extract('value', register=32, type='config read', chip=5)[-1]
origin()[source]

Return the original PacketCollection that this PacketCollection derives from.

with_chip_key(chip_key)[source]

Return packets with the specified chip key.

by_chip_key()[source]

Return a dict of { chipid: PacketCollection }.

with_chipid(chipid)[source]

Return packets with the specified chip ID.

by_chipid()[source]

Return a dict of { chipid: PacketCollection }.

Configuration registers

Configuration.register_names = ['pixel_trim_thresholds', 'global_threshold', 'csa_gain', 'csa_bypass', 'internal_bypass', 'csa_bypass_select', 'csa_monitor_select', 'csa_testpulse_enable', 'csa_testpulse_dac_amplitude', 'test_mode', 'cross_trigger_mode', 'periodic_reset', 'fifo_diagnostic', 'sample_cycles', 'test_burst_length', 'adc_burst_length', 'channel_mask', 'external_trigger_mask', 'reset_cycles']

This attribute lists the names of all available configuration registers. Each register name is available as its own attribute for inspecting and setting the value of the corresponding register.

Certain configuration values are set channel-by-channel. These are represented by a list of values. For example:

>>> conf.pixel_trim_thresholds[2:5]
[16, 16, 16]
>>> conf.channel_mask[20] = 1
>>> conf.external_trigger_mask = [0] * 32

Additionally, other configuration values take up more than or less than one complete register. These are still set by referencing the appropriate name. For example, cross_trigger_mode shares a register with a few other values, and adjusting the value of the cross_trigger_mode attribute will leave the other values unchanged.