diff --git a/libsigrokdecode4DSL/decoders/fsi/__init__.py b/libsigrokdecode4DSL/decoders/fsi/__init__.py new file mode 100755 index 00000000..8d589923 --- /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 +## +## 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 . +## + +''' +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 00000000..4b6b69f6 --- /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 +## +## 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 . +## + +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