575 lines
28 KiB
Python
Raw Normal View History

##
## 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