From 1de76d1e057ffa401924d3ae7d3248a9898b603a Mon Sep 17 00:00:00 2001
From: Timothy Pearson <tpearson@raptorengineering.com>
Date: Sun, 7 Jun 2020 15:48:35 -0500
Subject: [PATCH] Add initial FSI protocol decoder

The FSI protocol is used on OpenPOWER systems such as the
Raptor Computing Systems Talos II and Blackbird machines.
---
 libsigrokdecode4DSL/decoders/fsi/__init__.py |  25 +
 libsigrokdecode4DSL/decoders/fsi/pd.py       | 574 +++++++++++++++++++
 2 files changed, 599 insertions(+)
 create mode 100755 libsigrokdecode4DSL/decoders/fsi/__init__.py
 create mode 100755 libsigrokdecode4DSL/decoders/fsi/pd.py

diff --git a/libsigrokdecode4DSL/decoders/fsi/__init__.py b/libsigrokdecode4DSL/decoders/fsi/__init__.py
new file mode 100755
index 0000000..8d58992
--- /dev/null
+++ b/libsigrokdecode4DSL/decoders/fsi/__init__.py
@@ -0,0 +1,25 @@
+##
+## This file is part of the libsigrokdecode project.
+##
+## Copyright (C) 2020 Raptor Engineering, LLC <support@raptorengineering.com>
+##
+## This program is free software: you can redistribute it and/or modify
+## it under the terms of the GNU Affero General Public License as
+## published by the Free Software Foundation, either version 3 of the
+## License, or (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU Affero General Public License for more details.
+##
+## You should have received a copy of the GNU Affero General Public License
+## along with this program.  If not, see <https://www.gnu.org/licenses/>.
+##
+
+'''
+FSI is a low level serial protocol used by various devices on OpenPOWER
+systems such as the Raptor Talos II and Blackbird.
+'''
+
+from .pd import Decoder
diff --git a/libsigrokdecode4DSL/decoders/fsi/pd.py b/libsigrokdecode4DSL/decoders/fsi/pd.py
new file mode 100755
index 0000000..4b6b69f
--- /dev/null
+++ b/libsigrokdecode4DSL/decoders/fsi/pd.py
@@ -0,0 +1,574 @@
+##
+## This file is part of the libsigrokdecode project.
+##
+## Copyright (C) 2020 Raptor Engineering, LLC <support@raptorengineering.com>
+##
+## This program is free software: you can redistribute it and/or modify
+## it under the terms of the GNU Affero General Public License as
+## published by the Free Software Foundation, either version 3 of the
+## License, or (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU Affero General Public License for more details.
+##
+## You should have received a copy of the GNU Affero General Public License
+## along with this program.  If not, see <https://www.gnu.org/licenses/>.
+##
+
+import sigrokdecode as srd
+
+# ...
+fields = {
+    # START field (indicates the start of a transaction)
+    'START': {
+        0b1: 'Start of FSI cycle',
+    },
+}
+
+class Decoder(srd.Decoder):
+    api_version = 3
+    id = 'fsi'
+    name = 'FSI'
+    longname = 'Flexible Service Interface'
+    desc = 'Protocol for FSI devices on Raptor OpenPOWER systems.'
+    license = 'agplv3'
+    inputs = ['logic']
+    outputs = []
+    tags = ['PC']
+    channels = (
+        {'id': 'data',  'name': 'DATA',  'desc': 'Frame'},
+        {'id': 'clock', 'name': 'CLOCK', 'desc': 'Clock'},
+    )
+    annotations = (
+        ('warnings', 'Warnings'),
+        ('start', 'Start'),
+        ('cycle-type', 'Cycle type'),
+        ('direction', 'Direction'),
+        ('addr', 'Address'),
+        ('data', 'Data'),
+        ('commands', 'Commands'),
+        ('crc', 'CRC'),
+        ('turn-around', 'TAR'),
+    )
+    annotation_rows = (
+        ('data', 'Data', (1, 2, 3, 4, 5, 6, 7, 8,)),
+        ('warnings', 'Warnings', (0,)),
+    )
+
+    def __init__(self):
+        self.tar_cycles = 3
+        self.reset()
+
+    def reset(self):
+        self.state = 'IDLE'
+        self.break_start_sample_number = 0
+        self.break_counter = 0
+        self.samplenum = 0
+        self.samplenum_prev = 0
+        self.fsi_data_prev = 0
+        self.fsi_data_break_prev = 0
+        self.crc_internal = 0
+        self.response_received = 0
+        self.crc_calculating = 0
+        self.busy_seq_count = 0
+        self.valid_response = 0
+        self.prev_address = {}
+        self.prev_address_valid = {0: False, 1: False, 2: False, 3: False}
+        self.ss_block = None
+        self.es_block = None
+
+    def start(self):
+        self.out_ann = self.register(srd.OUTPUT_ANN)
+
+    def putb(self, data):
+        self.put(self.ss_block, self.es_block, self.out_ann, data)
+
+    def decode(self):
+        while True:
+            # FSI is clocked out on the falling edge and latched in on the rising edge of the clock...according to the specification.
+            # That said, the specification is not clear on what this actually means (and it doesn't follow industry convention).
+            # Real IBM POWER9 hardware is verified to work in the following mode:
+            # FSI data being sent to the master is latched by the master logic at the falling edge of the FSI clock
+            # FSI data being from to the master is strobed by the master logic at the falling edge of the FSI clock
+            # FSI data being sent to the slave is latched by the slave logic at the rising edge of the FSI clock
+            # FSI data being from to the slave is strobed by the slave logic at the rising edge of the FSI clock
+            #
+            # The above means that we have to be able to make a reasonable guess as to who is transmitting (the master or the slave)
+            # in order to know which edge to sample on
+
+            # Wait for either clock edge
+            (data, clk) = self.wait({1: 'e'})
+
+            # FSI data is electrically inverted
+            fsi_data = not data
+            fsi_clk = clk
+            current_sample_number = self.samplenum
+
+            # Detect BREAK commands
+            # Note these can only be sent by the master, so sample on the rising clock edge only
+            if (fsi_clk):
+                if (self.fsi_data_break_prev == 1):
+                    self.break_counter = self.break_counter + 1
+                    if (self.break_counter == 256):
+                        self.ss_block = self.break_start_sample_number
+                        self.es_block = current_sample_number
+                        self.putb([6, ['BREAK']])
+                        self.state = 'BREAK_TAR_QUEUED'
+                        self.busy_seq_count = 0
+                        self.valid_response = 0
+                        self.prev_address = {}
+                        self.prev_address_valid = {0: False, 1: False, 2: False, 3: False}
+                        self.ss_block = current_sample_number
+                else:
+                    if (self.break_counter > 256):
+                        self.es_block = current_sample_number
+                        self.putb([0, ['BREAK asserted in excess of specification cycles']])
+                    self.break_start_sample_number = current_sample_number
+                    self.break_counter = 0
+                self.fsi_data_break_prev = fsi_data
+
+            if ((self.state == 'TAR') or (self.state == 'RX_SLAVE_ID') or (self.state == 'RESPONSE') or (self.state == 'RX_DATA')
+                or (self.state == 'RX_IPOLL_INTERRUPT_FIELD') or (self.state == 'RX_IPOLL_DMA_CONTROL_FIELD') or (self.state == 'RX_IPOLL_DMA_CONTROL_FIELD')
+                or ((self.state == 'CRC') and (self.valid_response))):
+                # Slave is / should be transmitting, sample on falling clock edge only
+                if (fsi_clk):
+                    continue
+            else:
+                # Master is / should be transmitting, sample on rising clock edge only
+                if (not fsi_clk):
+                    continue
+
+            # Transfer state machine
+            if (self.state == 'IDLE'):
+                self.crc_internal = 0
+                self.response_received = 0
+                if (self.fsi_data_prev == 1):
+                    self.tx_slave_id = 0
+                    self.data_count = 2
+                    self.ss_block = self.samplenum_prev
+                    self.es_block = current_sample_number
+                    self.putb([1, ['START']])
+                    self.ss_block = current_sample_number
+                    self.crc_calculating = 1
+                    self.state = 'TX_SLAVE_ID'
+
+            elif (self.state == 'TX_SLAVE_ID'):
+                self.crc_calculating = 1
+                if (self.data_count > 0):
+                    self.tx_slave_id = (self.tx_slave_id >> 1) | (self.fsi_data_prev << 1)
+                    self.data_count = self.data_count - 1
+                    if (self.data_count == 0):
+                        self.es_block = current_sample_number
+                        self.putb([5, ['Slave ID: 0x%01x' % self.tx_slave_id]])
+                        self.ss_block = current_sample_number
+                        self.command_count = 0
+                        self.command_code = 0
+                        self.command = None
+                        self.valid_command = False
+                        self.state = 'COMMAND'
+
+            elif (self.state == 'COMMAND'):
+                self.crc_calculating = 1
+                self.command_code = (self.command_code << 1) | self.fsi_data_prev
+                self.command_count = self.command_count + 1
+                if ((self.command_count == 3) and (self.command_code == 0b100)):
+                    self.command = 'ABS_ADR'
+                    self.valid_command = True
+                elif ((self.command_count == 3) and (self.command_code == 0b101)):
+                    self.command = 'REL_ADR'
+                    self.valid_command = True
+                elif ((self.command_count == 2) and (self.command_code == 0b11)):
+                    self.command = 'SAME_ADR'
+                    self.valid_command = True
+                elif ((self.command_count == 3) and (self.command_code == 0b010)):
+                    self.command = 'D_POLL'
+                    self.valid_command = True
+                elif ((self.command_count == 3) and (self.command_code == 0b011)):
+                    self.command = 'E_POLL'
+                    self.valid_command = True
+                elif ((self.command_count == 3) and (self.command_code == 0b001)):
+                    self.command = 'I_POLL'
+                    self.valid_command = True
+                if ((self.command_count > 7) or (self.valid_command == True)):
+                    if (self.command_count == 8):
+                        self.es_block = current_sample_number
+                        self.putb([6, ['Invalid command code: 0x%02x/%d' % (self.command_code, self.command_count)]])
+                        self.putb([0, ['%s' % 'Invalid command code']])
+                        self.ss_block = current_sample_number
+                        self.state = 'IDLE'
+                    else:
+                        self.es_block = current_sample_number
+                        self.putb([6, ['Command: %s (0x%02x/%d)' % (self.command, self.command_code, self.command_count)]])
+                        self.ss_block = current_sample_number
+                        if (self.command == 'ABS_ADR'):
+                            self.address_length = 21
+                            self.address_count = 0
+                            self.address = 0
+                            self.state = 'DIRECTION'
+                        elif (self.command == 'REL_ADR'):
+                            self.address_length = 8
+                            self.address_count = 0
+                            self.address = 0
+                            self.state = 'DIRECTION'
+                        elif (self.command == 'SAME_ADR'):
+                            self.address_length = 2
+                            self.address_count = 0
+                            self.address = 0
+                            self.state = 'DIRECTION'
+                        elif (self.command == 'D_POLL'):
+                            self.crc = 0
+                            self.crc_count = 0
+                            self.state = 'CRC'
+                        elif (self.command == 'E_POLL'):
+                            self.crc = 0
+                            self.crc_count = 0
+                            self.state = 'CRC'
+                        elif (self.command == 'I_POLL'):
+                            self.crc = 0
+                            self.crc_count = 0
+                            self.state = 'CRC'
+                        else:
+                            self.state = 'IDLE'
+
+            elif (self.state == 'DIRECTION'):
+                self.crc_calculating = 1
+                self.direction = self.fsi_data_prev
+                self.es_block = current_sample_number
+                if (self.direction == 1):
+                    self.putb([3, ['Direction: %s' % 'Read']])
+                else:
+                    self.putb([3, ['Direction: %s' % 'Write']])
+                self.ss_block = current_sample_number
+                if (self.command == 'REL_ADR'):
+                    self.state = 'REL_ADDRESS_SIGN'
+                else:
+                    self.state = 'ADDRESS'
+
+            elif (self.state == 'REL_ADDRESS_SIGN'):
+                self.crc_calculating = 1
+                self.relative_address_negative = self.fsi_data_prev
+                self.es_block = current_sample_number
+                if (self.relative_address_negative == 1):
+                    self.putb([5, ['Relative address sign: %s' % '(-)']])
+                else:
+                    self.putb([5, ['Relative address sign: %s' % '(+)']])
+                self.ss_block = current_sample_number
+                self.state = 'ADDRESS'
+
+            elif (self.state == 'ADDRESS'):
+                self.crc_calculating = 1
+                self.address = (self.address << 1) | self.fsi_data_prev
+                self.address_count = self.address_count + 1
+                if (self.address_count >= self.address_length):
+                    self.address_raw = self.address
+                    if (self.prev_address_valid[self.tx_slave_id]):
+                        if (self.command == 'SAME_ADR'):
+                            self.address = (self.prev_address[self.tx_slave_id] & ~0b11) | (self.address_raw & 0b11)
+                        elif (self.command == 'REL_ADR'):
+                            if (self.relative_address_negative):
+                                self.address = self.prev_address[self.tx_slave_id] - (0x100 - self.address_raw)
+                            else:
+                                self.address = self.prev_address[self.tx_slave_id] + self.address_raw
+                    self.es_block = current_sample_number
+                    if (((self.command == 'SAME_ADR') or (self.command == 'REL_ADR'))):
+                        self.putb([5, ['Address: 0x%06x (0x%03x)' % (self.address, self.address_raw)]])
+                        if (not self.prev_address_valid[self.tx_slave_id]):
+                            self.putb([0, ['%s' % 'Base address for relative address not captured']])
+                    else:
+                        self.putb([5, ['Address: 0x%06x' % self.address]])
+                    self.ss_block = current_sample_number
+                    self.state = 'DATA_SIZE'
+
+            elif (self.state == 'DATA_SIZE'):
+                self.crc_calculating = 1
+                if (self.direction and ((self.address_raw & 3) == 3) and self.fsi_data_prev):
+                    # OpenFSI suffers from an unfortunate conflict between the SAME_ADR command
+                    # and the TERM command.  Both start with 2'b11 and since both are variable
+                    # length it is impossible to determine if a TERM command was sent until this
+                    # point in the receiver process!
+                    #
+                    # Set correct command code for further processing
+                    self.command_code = 0b111111
+                    self.command_count = 6
+                    self.command = 'TERM'
+                    self.es_block = current_sample_number
+                    self.putb([6, ['Command: %s (0x%02x/%d)' % (self.command, self.command_code, self.command_count)]])
+                    self.ss_block = current_sample_number
+                    self.direction = 0
+                    self.busy_seq_count = 0
+                    self.crc = 0
+                    self.crc_count = 0
+                    self.state = 'CRC'
+                else:
+                    if (self.fsi_data_prev == 0):
+                        self.data_size = 'BYTE'
+                    else:
+                        if ((self.address_raw & 3) == 1):
+                            self.data_size = 'WORD'
+                            # Force lowest address bits to specification-mandated values if required
+                            self.address = (self.address & ~3) | 1
+                        elif ((self.address_raw & 1) == 0):
+                            self.data_size = 'HALF_WORD'
+                            # Force lowest address bits to specification-mandated values if required
+                            self.address = self.address & ~1
+                        else:
+                            self.data_size = 'UNKNOWN'
+                    self.es_block = current_sample_number
+                    if (self.data_size == 'UNKNOWN'):
+                        self.putb([0, ['Data Size: %s' % 'UNKNOWN']])
+                        self.state = 'IDLE'
+                    else:
+                        self.putb([3, ['Data Size: %s' % self.data_size]])
+                        if (self.direction == 1):
+                            self.crc = 0
+                            self.crc_count = 0
+                            self.state = 'CRC'
+                        else:
+                            self.data = 0
+                            self.data_count = 0
+                            if (self.data_size == 'BYTE'):
+                                self.data_length = 8
+                            elif (self.data_size == 'HALF_WORD'):
+                                self.data_length = 16
+                            elif (self.data_size == 'WORD'):
+                                self.data_length = 32
+                            else:
+                                self.data_size = None
+                            self.state = 'TX_DATA'
+                self.ss_block = current_sample_number
+
+            elif (self.state == 'TX_DATA'):
+                self.crc_calculating = 1
+                self.data = (self.data << 1) | self.fsi_data_prev
+                self.data_count = self.data_count + 1
+                if (self.data_count >= self.data_length):
+                    self.es_block = current_sample_number
+                    if (self.data_size == 'BYTE'):
+                        self.putb([5, ['Data: 0x%02x' % self.data]])
+                    elif (self.data_size == 'HALF_WORD'):
+                        self.putb([5, ['Data: 0x%04x' % self.data]])
+                    else:
+                        self.putb([5, ['Data: 0x%08x' % self.data]])
+                    self.ss_block = current_sample_number
+                    self.crc = 0
+                    self.crc_count = 0
+                    self.state = 'CRC'
+
+            elif (self.state == 'CRC'):
+                if (self.crc_count == 0):
+                    self.computed_crc_tx_end = self.crc_internal
+                self.crc_calculating = 1
+                self.crc = (self.crc << 1) | self.fsi_data_prev
+                self.crc_count = self.crc_count + 1
+                if (self.crc_count >= 4):
+                    self.es_block = current_sample_number
+                    if (self.crc == self.computed_crc_tx_end):
+                        self.putb([7, ['CRC: 0x%01x (GOOD)' % self.crc]])
+                        if (self.response_received):
+                            if (((self.command == 'ABS_ADR') or (self.command == 'REL_ADR') or (self.command == 'SAME_ADR'))
+                                and ((self.response == 'ACK_D') or (self.response == 'ACK'))):
+                                self.prev_address[self.tx_slave_id] = self.address
+                                self.prev_address_valid[self.tx_slave_id] = True
+                    else:
+                        self.putb([7, ['CRC: 0x%01x (BAD)' % self.crc]])
+                        self.putb([0, ['%s' % 'Bad CRC']])
+                    self.ss_block = current_sample_number
+                    self.tar_timer = 0
+                    self.state = 'TAR'
+                    self.timeout_counter = 0
+
+            elif (self.state == 'BREAK_TAR_QUEUED'):
+                # Special case, since break operates outside of the main state machine
+                # This state is a safe entry point into the main state machine
+                self.tar_timer = 0
+                self.state = 'BREAK_TAR'
+
+            elif (self.state == 'BREAK_TAR'):
+                self.tar_timer = self.tar_timer + 1
+                if (self.tar_timer > self.tar_cycles):
+                    self.crc_calculating = 0
+                    self.crc_internal = 0
+                    self.es_block = current_sample_number
+                    self.putb([8, ['%s' % 'TAR']])
+                    self.ss_block = current_sample_number
+                    self.state = 'IDLE'
+
+            elif (self.state == 'TAR'):
+                self.crc_calculating = 0
+                self.crc_internal = 0
+                self.tar_timer = self.tar_timer + 1
+                if (self.tar_timer > self.tar_cycles):
+                    if (self.response_received == 1):
+                        self.response_received = 0
+                        if (self.rx_slave_id == self.tx_slave_id):
+                            if (self.response == 'BUSY'):
+                                self.busy_seq_count = self.busy_seq_count + 1
+                            else:
+                                self.busy_seq_count = 0
+                            # Sequence complete
+                            self.state = 'IDLE'
+                    if (self.timeout_counter == 0):
+                        self.es_block = self.samplenum_prev
+                        self.putb([8, ['%s' % 'TAR']])
+                        self.ss_block = current_sample_number
+                    if (self.fsi_data_prev == 1):
+                        self.crc_calculating = 1
+                        self.rx_slave_id = 0
+                        self.data_count = 2
+                        if (self.state == 'IDLE'):
+                            # Already processed response message, was going to IDLE state
+                            self.state = 'TX_SLAVE_ID'
+                        else:
+                            self.state = 'RX_SLAVE_ID'
+                        self.ss_block = self.samplenum_prev
+                        self.es_block = current_sample_number
+                        self.putb([1, ['START']])
+                        self.ss_block = current_sample_number
+                    else:
+                        self.timeout_counter = self.timeout_counter + 1
+                        if (self.timeout_counter >= 256):
+                            self.es_block = current_sample_number
+                            self.putb([8, ['%s' % 'Response timeout']])
+                            self.putb([0, ['%s' % 'Response timeout']])
+                            self.state = 'IDLE'
+
+            elif (self.state == 'RX_SLAVE_ID'):
+                self.crc_calculating = 1
+                self.response_received = 1
+                if (self.data_count > 0):
+                    self.rx_slave_id = (self.rx_slave_id >> 1) | (self.fsi_data_prev << 1)
+                    self.data_count = self.data_count - 1
+                    if (self.data_count == 0):
+                        self.es_block = current_sample_number
+                        self.putb([5, ['Slave ID: 0x%01x' % self.rx_slave_id]])
+                        if (self.rx_slave_id != self.tx_slave_id):
+                            self.putb([0, ['%s' % 'Slave ID does not match active transaction']])
+                        self.ss_block = current_sample_number
+                        self.response_count = 0
+                        self.response_code = 0
+                        self.response = None
+                        self.valid_response = False
+                        self.state = 'RESPONSE'
+
+            elif (self.state == 'RESPONSE'):
+                self.crc_calculating = 1
+                self.response_code = (self.response_code << 1) | self.fsi_data_prev
+                self.response_count = self.response_count + 1
+                if ((self.command == 'I_POLL') and (self.rx_slave_id == self.tx_slave_id) and (self.response_count == 1) and (self.response_code == 0b0)):
+                    self.response = 'I_POLL_RSP'
+                    self.valid_response = True
+                elif ((self.response_count == 2) and (self.response_code == 0b00)):
+                    if (self.direction == 1):
+                        self.response = 'ACK_D'
+                    else:
+                        self.response = 'ACK'
+                    self.valid_response = True
+                elif ((self.response_count == 2) and (self.response_code == 0b01)):
+                    self.response = 'BUSY'
+                    self.valid_response = True
+                elif ((self.response_count == 2) and (self.response_code == 0b10)):
+                    self.response = 'ERR_A'
+                    self.valid_response = True
+                elif ((self.response_count == 2) and (self.response_code == 0b11)):
+                    self.response = 'ERR_C'
+                    self.valid_response = True
+                if ((self.response_count > 2) or (self.valid_response == True)):
+                    if (self.response_count == 8):
+                        self.es_block = current_sample_number
+                        self.putb([6, ['Invalid response code: 0x%02x/%d' % (self.response_code, self.response_count)]])
+                        self.putb([0, ['%s' % 'Invalid response code']])
+                        self.ss_block = current_sample_number
+                        self.state = 'IDLE'
+                    else:
+                        self.es_block = current_sample_number
+                        self.putb([6, ['Response: %s (0x%02x/%d)' % (self.response, self.response_code, self.response_count)]])
+                        self.ss_block = current_sample_number
+                        if (self.response == 'ACK_D'):
+                            self.data = 0
+                            self.data_count = 0
+                            if (self.data_size == 'BYTE'):
+                                self.data_length = 8
+                            elif (self.data_size == 'HALF_WORD'):
+                                self.data_length = 16
+                            elif (self.data_size == 'WORD'):
+                                self.data_length = 32
+                            else:
+                                self.data_size = None
+                            #self.state = 'DIRECTION'
+                            self.state = 'RX_DATA'
+                        elif (self.response == 'ACK'):
+                            self.crc = 0
+                            self.crc_count = 0
+                            self.state = 'CRC'
+                        elif (self.response == 'BUSY'):
+                            self.crc = 0
+                            self.crc_count = 0
+                            self.state = 'CRC'
+                        elif (self.response == 'ERR_A'):
+                            self.crc = 0
+                            self.crc_count = 0
+                            self.state = 'CRC'
+                        elif (self.response == 'ERR_C'):
+                            self.crc = 0
+                            self.crc_count = 0
+                            self.state = 'CRC'
+                        elif (self.response == 'I_POLL_RSP'):
+                            self.data = 0
+                            self.data_count = 0
+                            self.data_length = 2
+                            self.state = 'RX_IPOLL_INTERRUPT_FIELD'
+                        else:
+                            self.state = 'IDLE'
+
+            elif (self.state == 'RX_DATA'):
+                self.crc_calculating = 1
+                self.data = (self.data << 1) | self.fsi_data_prev
+                self.data_count = self.data_count + 1
+                if (self.data_count >= self.data_length):
+                    self.es_block = current_sample_number
+                    self.putb([5, ['Data: 0x%08x' % self.data]])
+                    self.ss_block = current_sample_number
+                    self.crc = 0
+                    self.crc_count = 0
+                    self.state = 'CRC'
+
+            elif (self.state == 'RX_IPOLL_INTERRUPT_FIELD'):
+                self.crc_calculating = 1
+                self.data = (self.data << 1) | self.fsi_data_prev
+                self.data_count = self.data_count + 1
+                if (self.data_count >= self.data_length):
+                    self.es_block = current_sample_number
+                    self.putb([5, ['Interrupt Field: 0x%01x' % self.data]])
+                    self.ss_block = current_sample_number
+                    self.data = 0
+                    self.data_count = 0
+                    self.data_length = 3
+                    self.state = 'RX_IPOLL_DMA_CONTROL_FIELD'
+
+            elif (self.state == 'RX_IPOLL_DMA_CONTROL_FIELD'):
+                self.crc_calculating = 1
+                self.data = (self.data << 1) | self.fsi_data_prev
+                self.data_count = self.data_count + 1
+                if (self.data_count >= self.data_length):
+                    self.es_block = current_sample_number
+                    self.putb([5, ['DMA Control Field: 0x%01x' % self.data]])
+                    self.ss_block = current_sample_number
+                    self.crc = 0
+                    self.crc_count = 0
+                    self.state = 'CRC'
+
+            # CRC calculation
+            # Implement Galios-type LFSR for polynomial 0x7 (MSB first)
+            crc_prev = self.crc_internal
+            if (self.crc_calculating):
+                crc_feedback = (((crc_prev >> 3) & 1) ^ self.fsi_data_prev) & 1
+            if (self.crc_calculating):
+                self.crc_internal = (self.crc_internal & ~(1 << 0)) | ((crc_feedback & 1) << 0)
+                self.crc_internal = (self.crc_internal & ~(1 << 1)) | ((((crc_prev & 1) ^ crc_feedback) & 1) << 1)
+                self.crc_internal = (self.crc_internal & ~(1 << 2)) | (((((crc_prev >> 1) & 1) ^ crc_feedback) & 1) << 2)
+                self.crc_internal = (self.crc_internal & ~(1 << 3)) | ((((crc_prev >> 2) & 1) & 1) << 3)
+
+            self.fsi_data_prev = fsi_data
+            self.samplenum_prev = current_sample_number
-- 
GitLab