Closed chongxi closed 4 years ago
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.
Two relevant variable reused from #58 is:
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)
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)
Finally, the above method will be deprecated when the trigger neuron selection problem is solved at the FPGA level.
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)
Test Results on Hardware: (the DUT is the BMI under test)
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:
When triggered at real neuron's spike:
This version also works for population decoding
. But it requires fpga.target_unit = 0
The spike identity is dependent on the output of the
label
by the classifier. The classifier adopts kNN algorithm on the vector quantizedvq
and itslabel
. Each group contains 500vq
and its associatedlabels
. For a given transformed spike, the classifier outputs its nearestvq
'slabel
.