Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: GlasgowEmbedded/glasgow
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 130f00418029
Choose a base ref
...
head repository: GlasgowEmbedded/glasgow
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 2199108954ed
Choose a head ref
  • 2 commits
  • 1 file changed
  • 1 contributor

Commits on Jul 26, 2020

  1. applet.memory.prom: print values of unstable words.

    This can help e.g. with distinguishing signal integrity issues from
    bit rot issues.
    whitequark committed Jul 26, 2020

    Verified

    This commit was signed with the committer’s verified signature.
    issyl0 Issy Long
    Copy the full SHA
    33b3a62 View commit details

Commits on Jul 27, 2020

  1. applet.memory.prom: add health histogram command.

    With the accompanying tool.
    whitequark committed Jul 27, 2020
    Copy the full SHA
    2199108 View commit details
Showing with 114 additions and 8 deletions.
  1. +114 −8 software/glasgow/applet/memory/prom/__init__.py
122 changes: 114 additions & 8 deletions software/glasgow/applet/memory/prom/__init__.py
Original file line number Diff line number Diff line change
@@ -2,12 +2,14 @@
# Accession: G00057

import re
import math
import enum
import math
import json
import random
import logging
import asyncio
import argparse
import statistics
from nmigen import *
from nmigen.lib.cdc import FFSynchronizer
from nmigen.lib.fifo import SyncFIFOBuffered
@@ -302,7 +304,11 @@ def difference(self, other):
raw_diff = ((int.from_bytes(self.raw_data, "little") ^
int.from_bytes(other.raw_data, "little"))
.to_bytes(len(self.raw_data), "little"))
return set(m.start() // self.dq_bytes for m in re.finditer(rb"[^\x00]", raw_diff))
diff = dict()
for m in re.finditer(rb"[^\x00]", raw_diff):
index = m.start() // self.dq_bytes
diff[index] = (self[index], other[index])
return diff

def __init__(self, interface, logger, a_bits, dq_bits):
self.lower = interface
@@ -463,6 +469,11 @@ def address(arg):
return int(arg, 0)
def length(arg):
return int(arg, 0)
def voltage_range(arg):
m = re.match(r"^(\d+(?:\.\d*)?):(\d+(?:\.\d*)?)$", arg)
if not m:
raise argparse.ArgumentTypeError("'{}' is not a voltage range".format(arg))
return float(m[1]), float(m[2])

p_operation = parser.add_subparsers(dest="operation", metavar="OPERATION", required=True)

@@ -533,7 +544,22 @@ def length(arg):
help="read entire memory COUNT times (default: %(default)s)")
p_health_sweep.add_argument(
"--voltage-step", metavar="STEP", type=float, default=0.05,
help="reduce supply voltage by STEP volts")
help="reduce supply voltage by STEP volts (default: %(default)s)")

p_health_histogram = p_health_mode.add_parser(
"histogram", help="sample population count for a voltage range")
p_health_histogram.add_argument(
"--samples", metavar="COUNT", type=int, default=5,
help="average population count COUNT times (default: %(default)s)")
p_health_histogram.add_argument(
"--sweep", metavar="START:END", type=voltage_range, required=True,
help="sweep supply voltage from START to END volts")
p_health_histogram.add_argument(
"--voltage-step", metavar="STEP", type=float, default=0.05,
help="change supply voltage by STEP volts (default: %(default)s)")
p_health_histogram.add_argument(
"file", metavar="FILENAME", type=argparse.FileType("wt"),
help="write aggregated data to FILENAME")

async def interact(self, device, args, prom_iface):
a_bits = args.a_bits
@@ -572,8 +598,9 @@ async def interact(self, device, args, prom_iface):

current_data = await prom_iface.read_shuffled(0, depth)
current_unstable = initial_data.difference(current_data)
for index in sorted(current_unstable - unstable):
self.logger.warning("word %#x unstable", index)
for index in sorted(set(current_unstable) - unstable):
self.logger.warning("word %#x unstable (%#x != %#x)",
index, initial_data[index], current_data[index])
unstable.update(current_unstable)

if unstable:
@@ -594,8 +621,9 @@ async def interact(self, device, args, prom_iface):

current_data = await prom_iface.read_shuffled(0, depth)
current_unstable = initial_data.difference(current_data)
for index in sorted(current_unstable - unstable):
self.logger.warning("word %#x unstable", index)
for index in sorted(set(current_unstable) - unstable):
self.logger.warning("word %#x unstable (%#x != %#x)",
index, initial_data[index], current_data[index])
consecutive = 0
unstable.update(current_unstable)

@@ -625,7 +653,8 @@ async def interact(self, device, args, prom_iface):
current_data = await prom_iface.read_shuffled(0, depth)
unstable = initial_data.difference(current_data)
for index in sorted(unstable):
self.logger.warning(" word %#x unstable", index)
self.logger.warning("word %#x unstable (%#x != %#x)",
index, initial_data[index], current_data[index])
if unstable:
self.logger.warning("step %d FAIL (%d words unstable)",
step_num, len(unstable))
@@ -639,6 +668,83 @@ async def interact(self, device, args, prom_iface):

self.logger.info("health %s PASS at %.2f V", args.mode, voltage)

if args.operation == "health" and args.mode == "histogram":
voltage_from, voltage_to = args.sweep
popcount_lut = [format(n, "b").count("1") for n in range(1 << dq_bits)]

histogram = []
voltage = voltage_from
step_num = 0
while True:
self.logger.info("step %d (%.2f V)", step_num, voltage)
await device.set_voltage(args.port_spec, voltage)

popcounts = []
for sample_num in range(args.samples):
self.logger.info(" sample %d", sample_num)
data = await prom_iface.read_shuffled(0, depth)
popcounts.append(sum(popcount_lut[word] for word in data))

histogram.append((voltage, popcounts))
self.logger.info("population %d/%d",
sum(popcounts) // len(popcounts),
depth * dq_bits)

if voltage_to > voltage_from:
voltage += args.voltage_step
if voltage > voltage_to: break
else:
voltage -= args.voltage_step
if voltage < voltage_to: break
step_num += 1

json.dump({
"density": depth * dq_bits,
"histogram": [
{"voltage": voltage, "popcounts": popcounts}
for voltage, popcounts in histogram
]
}, args.file)

# -------------------------------------------------------------------------------------------------

class MemoryPROMAppletTool(GlasgowAppletTool, applet=MemoryPROMApplet):
help = "display statistics of parallel EPROMs, EEPROMs, and Flash memories"
description = """
Display statistics collected of parallel memories collected with the `health` subcommand.
"""

@classmethod
def add_arguments(cls, parser):
p_operation = parser.add_subparsers(dest="operation", metavar="OPERATION", required=True)

p_histogram = p_operation.add_parser(
"histogram", help="plot population count for a voltage range")
p_histogram.add_argument(
"file", metavar="FILENAME", type=argparse.FileType("rt"),
help="read aggregated data from FILENAME")

async def run(self, args):
if args.operation == "histogram":
data = json.load(args.file)
density = data["density"]
histogram = [
(row["voltage"], row["popcounts"], statistics.mean(row["popcounts"]))
for row in data["histogram"]
]

min_popcount = min(mean_popcounts for _, _, mean_popcounts in histogram)
max_popcount = max(mean_popcounts for _, _, mean_popcounts in histogram)
histogram_size = 40
resolution = (max_popcount - min_popcount) / histogram_size
print(f" {str(min_popcount):<{1 + histogram_size // 2}s}"
f"{str(max_popcount):>{1 + histogram_size // 2}s}")
for voltage, popcounts, mean_popcount in histogram:
rectangle_size = math.floor((mean_popcount - min_popcount) / resolution)
print(f"{voltage:.2f}: |{'=' * rectangle_size:{histogram_size}s}| "
f"({len(popcounts)}× {int(mean_popcount)}/{density}, "
f"sd {statistics.pstdev(popcounts):.2f})")

# -------------------------------------------------------------------------------------------------

class MemoryPROMAppletTestCase(GlasgowAppletTestCase, applet=MemoryPROMApplet):