chongxi / spiketag

Next generation of spike sorting package for BMI
BSD 3-Clause "New" or "Revised" License
6 stars 4 forks source link

spike-triggered TTL (FPGA bit file attached) #63

Closed chongxi closed 4 years ago

chongxi commented 4 years ago

The spike identity is dependent on the output of the label by the classifier. The classifier adopts kNN algorithm on the vector quantized vq and its label. Each group contains 500 vq and its associated labels. For a given transformed spike, the classifier outputs its nearest vq's label.

image

chongxi commented 4 years ago

The bit file for the FPGA here:

This version provides 160 channels recording, real-time spike sorting and single-spikes-triggered TTL (1ms) function. Also, it supports population activity decoder #57 #58 spi_xike_pcie.zip Note: Change zip to bit before downloading to the FPGA

This version always generates a TTL pulse (1ms) when neuron 101 fires.

chongxi commented 4 years ago

Two relevant variable reused from #58 is:

chongxi commented 4 years ago

fpga.label.to_numpy() returns a 40 by 500 matrix. 40 rows correspond to 40 groups (404=160 channels), where each row is a 500 length vq-labels from that group. The label 0 always means noise, whereas the label larger than 0 is unique for each vq and means a sorted unit. The rows that have all 0 vectors indicate those groups have no sorted units. In the below example, we have sorted-units from 1-79* and each group contains noise with label 0.

In [2]: fpga.label.to_numpy()                                                                                            
Out[2]: 
array([[ 0,  0,  0, ...,  0,  0,  0],
       [ 0,  0,  0, ...,  8,  8,  8],
       [ 0,  0,  0, ..., 16, 16, 16],
       ...,
       [ 0,  0,  0, ..., 74, 74, 76],
       [ 0,  0,  0, ...,  0,  0,  0],
       [ 0,  0,  0, ..., 79, 79, 79]], dtype=int32)

One can flexibily create a new label_matrix with same shape (40,500) and use from_numpy to configure the FPGA from PC.

In [3]: fpga.label.from_numpy(label_matrix)                                                                                            
chongxi commented 4 years ago

In order to select a neuron as triggered neuron, we need to change its label to 101. This can be done using the above to_numpy and from_numpy APIs.

It is implemented in the bmi.fpga.target_unit. The target_unit, as an integer number, is cached into the 8th slot of mem_16 memory block in the FPGA. The fpga.label that matches the targert_unit are replaced by 101.

    @target_unit.setter
    def target_unit(self, target_unit_id):
        '''
        after ctrl.compile(), the target_unit is set to 0 by default
        '''
        if target_unit_id != 0:   # set the target_unit
            previous_target_unit_id = read_mem_16(8)
            label_matrix = self.label.to_numpy() # (40,500) matrix
            if previous_target_unit_id != 0:
                label_matrix[label_matrix==101] = previous_target_unit_id
            label_matrix[label_matrix==target_unit_id] = 101
            self.label.from_numpy(label_matrix)
        else:   # reset 
            previous_target_unit_id = read_mem_16(8)
            label_matrix = self.label.to_numpy() # (40,500) matrix
            label_matrix[label_matrix==101] = previous_target_unit_id
            self.label.from_numpy(label_matrix)
        write_mem_16(8, target_unit_id)
chongxi commented 4 years ago

Finally, the above method will be deprecated when the trigger neuron selection problem is solved at the FPGA level.

chongxi commented 4 years ago

Software/Hardware Interface Test Result:

In [2]: fpga.label.to_numpy()                                                                                                                                                                                                                                                                          
Out[2]: 
array([[ 0,  0,  0, ...,  0,  0,  0],
       [ 0,  0,  0, ...,  8,  8,  8],
       [ 0,  0,  0, ..., 16, 16, 16],
       ...,
       [ 0,  0,  0, ..., 74, 74, 76],
       [ 0,  0,  0, ...,  0,  0,  0],
       [ 0,  0,  0, ..., 79, 79, 79]], dtype=int32)

In [3]: fpga.target_unit = 8; fpga.label.to_numpy()                                                                                                                                                                                                                                                    
Out[3]: 
array([[  0,   0,   0, ...,   0,   0,   0],
       [  0,   0,   0, ..., 101, 101, 101],
       [  0,   0,   0, ...,  16,  16,  16],
       ...,
       [  0,   0,   0, ...,  74,  74,  76],
       [  0,   0,   0, ...,   0,   0,   0],
       [  0,   0,   0, ...,  79,  79,  79]], dtype=int32)

In [4]: fpga.target_unit = 16; fpga.label.to_numpy()                                                                                                                                                                                                                                                   
Out[4]: 
array([[  0,   0,   0, ...,   0,   0,   0],
       [  0,   0,   0, ...,   8,   8,   8],
       [  0,   0,   0, ..., 101, 101, 101],
       ...,
       [  0,   0,   0, ...,  74,  74,  76],
       [  0,   0,   0, ...,   0,   0,   0],
       [  0,   0,   0, ...,  79,  79,  79]], dtype=int32)

In [5]: fpga.target_unit = 74; fpga.label.to_numpy()                                                                                                                                                                                                                                                   
Out[5]: 
array([[  0,   0,   0, ...,   0,   0,   0],
       [  0,   0,   0, ...,   8,   8,   8],
       [  0,   0,   0, ...,  16,  16,  16],
       ...,
       [  0,   0,   0, ..., 101, 101,  76],
       [  0,   0,   0, ...,   0,   0,   0],
       [  0,   0,   0, ...,  79,  79,  79]], dtype=int32)

In [6]: fpga.target_unit = 79; fpga.label.to_numpy()                                                                                                                                                                                                                                                   
Out[6]: 
array([[  0,   0,   0, ...,   0,   0,   0],
       [  0,   0,   0, ...,   8,   8,   8],
       [  0,   0,   0, ...,  16,  16,  16],
       ...,
       [  0,   0,   0, ...,  74,  74,  76],
       [  0,   0,   0, ...,   0,   0,   0],
       [  0,   0,   0, ..., 101, 101, 101]], dtype=int32)

In [7]: fpga.target_unit = 0; fpga.label.to_numpy()                                                                                                                                                                                                                                                    
Out[7]: 
array([[ 0,  0,  0, ...,  0,  0,  0],
       [ 0,  0,  0, ...,  8,  8,  8],
       [ 0,  0,  0, ..., 16, 16, 16],
       ...,
       [ 0,  0,  0, ..., 74, 74, 76],
       [ 0,  0,  0, ...,  0,  0,  0],
       [ 0,  0,  0, ..., 79, 79, 79]], dtype=int32)
chongxi commented 4 years ago

Test Results on Hardware: (the DUT is the BMI under test)

image

To decide which neuron to trigger, we only need to set: fpga.target_unit = x

When triggered at an artificially synthesized neuron fires each 160ms:

image

Example

When triggered at real neuron's spike: image

chongxi commented 4 years ago

Note

This version also works for population decoding. But it requires fpga.target_unit = 0