Skip to content

Commit

Permalink
Add QSPI and continuation support
Browse files Browse the repository at this point in the history
This uses the Simple Parallel low level analyzer as input and
guesstimates where CS is raised based on transmission pauses.
  • Loading branch information
tannewt committed Mar 19, 2021
1 parent ce45651 commit eade7cd
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 52 deletions.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,28 @@ Under the three dot, more menu, click `Check for Updates` then install the `SPI
2. Under the three dot, more menu, click `Load Existing Extension...` and then select the local repo location.

## Usage

### Normal SPI
1. Click the analyzers tab.
2. Add a `SPI` analyzer and configure it for your capture.
3. Add a `SPI Flash` analyzer.
4. Set the `SPI` analyzer as the Input Analyzer.
5. `Min Address`, `Max Address` and `Decode Level` are optional.
6. Change `Address Bytes` to match
6. Change `Address Bytes` to match

### Quad SPI
1. Click the analyzers tab.
2. Add a `Simple Parallel` analyzer. (You may need to search for it.)
3. Configure it for your capture.
1. Set D0 to MOSI.
2. Set D1 to MISO.
3. Set D2 to IO2 (WP on 8 pin flashes usually.)
4. Set D3 to IO3 (HOLD on 8 pin flashes usually.)
5. Set D15 to CS (used to ignore extra clocks.)
6. Set the clock to the clock pin with the correct edge set.
4. Add a `SPI Flash` analyzer.
5. Set the `Simple Parallel` analyzer as the Input Analyzer.
6. `Min Address`, `Max Address` and `Decode Level` are optional.
7. Change `Address Bytes` to match

Note: When using Simple Parallel input, it is assumed the CS line goes high between parallel captures that are greater than 4 times the time separation of the closest clocks seen thus far. So, beware of spurious clocks and SPI transmissions that pause between bytes but leave CS low. This analyzer may incorrectly partition the transactions.
217 changes: 166 additions & 51 deletions SPIFlashAnalyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,34 @@

import struct

# value is dummy clocks
CONTINUE_COMMANDS = {
0xe7: 2,
0xeb: 4,
}

DATA_COMMANDS = {0x03: "Read",
0x0b: "Fast Read",
0xe7: "Quad Word Read",
0xeb: "Quad Read",
0x02: "Page Program",
0x32: "Quad Page Program"}

CONTROL_COMMANDS = {
0x01: "Write Status Register 1",
0x06: "Write Enable",
0x05: "Read Status Register",
0x35: "Read Status Register 2",
0x75: "Program Suspend"
}

class FakeFrame:
def __init__(self, t, time=None):
self.type = t
self.start_time = time
self.end_time = time
self.data = {}

# High level analyzers must subclass the HighLevelAnalyzer class.
class SPIFlash(HighLevelAnalyzer):
# List of settings that a user can set for this High Level Analyzer.
Expand Down Expand Up @@ -44,69 +61,167 @@ def __init__(self):
Settings can be accessed using the same name used above.
'''
self._start_time = None
self._address_format = "{:0" + str(2*int(self.address_bytes)) + "x}"
self._address_bytes = 3
self._address_format = "{:0" + str(2*int(self._address_bytes)) + "x}"
self._min_address = int(self.min_address)
self._max_address = None
if self.max_address:
self._max_address = int(self.max_address)

self._miso_data = None
self._mosi_data = None
self._empty_result_count = 0

# These are for quad decoding. The input will be a SimpleParallel analyzer
# with the correct clock edge. CS is inferred from a gap in time.
self._last_cs = 1
self._last_time = None
self._transaction = 0
self._clock_count = 0
self._mosi_out = 0
self._miso_in = 0
self._quad_data = 0
self._quad_start = None
self._continuous = False
self._dummy = 0

self._fastest_cs = 2000000


def decode(self, frame: AnalyzerFrame):
'''
Process a frame from the input analyzer, and optionally return a single `AnalyzerFrame` or a list of `AnalyzerFrame`s.
The type and data values in `frame` will depend on the input analyzer.
'''

if frame.type == "enable":
self._start_time = frame.start_time
self._miso_data = bytearray()
self._mosi_data = bytearray()
elif frame.type == "result":
if self._miso_data is None or self._mosi_data is None:
print(frame)
return
self._miso_data.extend(frame.data["miso"])
self._mosi_data.extend(frame.data["mosi"])
elif frame.type == "disable":
if not self._miso_data or not self._mosi_data:
return
command = self._mosi_data[0]
frame_type = None
frame_data = {"command": command}
if command in DATA_COMMANDS:
if len(self._mosi_data) < 1 + int(self.address_bytes):
frame_type = "error"
else:
frame_type = "data_command"
frame_data["command"] = DATA_COMMANDS[command]
frame_address = 0
for i in range(int(self.address_bytes)):
frame_address <<= 8
frame_address += self._mosi_data[1+i]
if self.min_address > 0 and frame_address < self._min_address:
frame_type = None
elif self.max_address and frame_address > self.max_address:
frame_type = None
else:
frame_data["address"] = self._address_format.format(frame_address)
# Support getting data from a Simple Parallel and converting it.
frames = []
if frame.type == "data":
data = frame.data["data"]
cs = data >> 15
if self._last_time:
diff = frame.start_time - self._last_time
else:
if command in CONTROL_COMMANDS:
frame_data["command"] = CONTROL_COMMANDS[command]
frame_type = "control_command"
our_frame = None
if frame_type:
our_frame = AnalyzerFrame(frame_type,
self._start_time,
frame.end_time,
frame_data)
self._miso_data = None
self._mosi_data = None
if self.decode_level == 'Only Data' and frame_type == "control_command":
return None
if self.decode_level == 'Only Errors' and frame_type != "error":
return None
if self.decode_level == "Only Control" and frame_type != "control_command":
diff = self._fastest_cs
diff = float(diff * 1_000_000_000)

self._fastest_cs = min(diff * 4, self._fastest_cs)
if diff > self._fastest_cs and cs == 0:
if self._transaction > 0:
frames.append(FakeFrame("disable", self._last_time))

frames.append(FakeFrame("enable", frame.start_time))

self._transaction += 1
self._clock_count = 0
if not self._continuous:
self._command = 0
self._quad_start = None
self._dummy = 0
else:
self._clock_count = 8
f = FakeFrame("result")
f.data["mosi"] = [self._command]
f.data["miso"] = [0]
frames.append(f)

self._last_time = frame.start_time

# TODO: We could output clock counts when cs is high.
if cs == 1:
return None
return our_frame

if self._quad_start is None or self._clock_count < self._quad_start:
self._mosi_out = self._mosi_out << 1 | (data & 0x1)
self._miso_in = self._miso_in << 1 | ((data >> 1) & 0x1)
if self._clock_count % 8 == 7:
if self._clock_count == 7:
self._command = self._mosi_out
if self._command in CONTINUE_COMMANDS:
self._quad_start = 8
self._dummy = CONTINUE_COMMANDS[self._command]

f = FakeFrame("result")
f.data["mosi"] = [self._mosi_out]
f.data["miso"] = [self._miso_in]
frames.append(f)
self._mosi_out = 0
self._miso_in = 0
else:
self._quad_data = (self._quad_data << 4 | data & 0xf)
if self._clock_count % 2 == 1:

f = FakeFrame("result")
if not 15 < self._clock_count <= 15 + self._dummy:
f.data["mosi"] = [self._quad_data]
f.data["miso"] = [0]
else:
f.data["mosi"] = [0]
f.data["miso"] = [self._quad_data]
frames.append(f)
if self._command in CONTINUE_COMMANDS and self._clock_count == 15:
self._continuous = self._quad_data == 0xa0
self._quad_data = 0

self._clock_count += 1
else:
print(frame)
print("non data!")
frames = [frame]

output = None
for fake_frame in frames:
if fake_frame.type == "enable":
self._start_time = fake_frame.start_time
self._miso_data = bytearray()
self._mosi_data = bytearray()
elif fake_frame.type == "result":
if self._miso_data is None or self._mosi_data is None:
if self._empty_result_count == 0:
print(fake_frame)
self._empty_result_count += 1
continue
self._miso_data.extend(fake_frame.data["miso"])
self._mosi_data.extend(fake_frame.data["mosi"])
elif fake_frame.type == "disable":
if not self._miso_data or not self._mosi_data:
continue
command = self._mosi_data[0]
frame_type = None
frame_data = {"command": command}
if command in DATA_COMMANDS:
if len(self._mosi_data) < 1 + int(self._address_bytes):
frame_type = "error"
else:
frame_type = "data_command"
frame_data["command"] = DATA_COMMANDS[command]
frame_address = 0
for i in range(int(self._address_bytes)):
frame_address <<= 8
frame_address += self._mosi_data[1+i]
if self.min_address > 0 and frame_address < self._min_address:
frame_type = None
elif self.max_address and frame_address > self.max_address:
frame_type = None
else:
frame_data["address"] = self._address_format.format(frame_address)
else:
if command in CONTROL_COMMANDS:
frame_data["command"] = CONTROL_COMMANDS[command]
frame_type = "control_command"
our_frame = None
if frame_type:
our_frame = AnalyzerFrame(frame_type,
self._start_time,
fake_frame.end_time,
frame_data)
self._miso_data = None
self._mosi_data = None
if self.decode_level == 'Only Data' and frame_type == "control_command":
continue
if self.decode_level == 'Only Errors' and frame_type != "error":
continue
if self.decode_level == "Only Control" and frame_type != "control_command":
continue
output = our_frame
return output

0 comments on commit eade7cd

Please sign in to comment.