Source code for pylab_ml.common.memorytest

# -*- coding: utf-8 -*-
"""
Created on Fri Aug  8 15:18:15 2025

This script is for memory testing, it defines a class MER to hold the operations for the memory test and a class Memory_test to perform the memory test based on the operations defined in the MER. 
The Memory_test class includes methods to run different algorithms for memory testing and to read/write memory using provided functions.

@author: Zlin526F
"""
import math
from time import time
from pylab_ml.common.data import complement


[docs] class MER: """ March Element Register, holds the operations for the memory test. The MER has 4 registers (r0, r1, w0, w1) which can be set to read or write operations. """ SIZE = 4 pmer = ["r0", "r1", "w0", "w1"]
[docs] def __init__(self, log_error): self.log_error = log_error self._values = ["read 0"] * self.SIZE # define 4 March Element Register, default value = "read 0"
def __getitem__(self, index): if not isinstance(index, int) or not (1 <= index < self.SIZE+1): self.log_error(f"Index={index}, must be between 1 and {self.SIZE}") return self._values[index-1] def __setitem__(self, index, value): if not isinstance(index, int) or not (1 <= index < self.SIZE+1): self.log_error(f"Index must be between 1 and {self.SIZE}") return if not isinstance(value, str) or value not in self.pmer: self.log_error(f'Value must be {self.pmer}') return trvalue = 'read ' if value[0] == 'r' else 'write ' trvalue = trvalue + value[1] self._values[index-1] = trvalue for i in range(index, self.SIZE): self._values[i] = None self.cnt = index
[docs] class Memory_test(): """ Memory test class, performs memory testing based on the operations defined in the MER. TODO: - only data background = sdb implemented, missing: 'bdb', 'rdb', 'cdb' - Address counting according to the actual layout structure is missing. - bin x = linear counting - bin y missing - operation next missing - operation hammer missing """ pdata_background = ['sdb', # solid DB - all bits with same data 'bdb' # checkerboard DB - adjacent cells with different data not implemented 'rdb' # row stripes DB not implemented 'cdb' # column striped DB not implemented ] pcount_method = ['bin x', 'complement'] padr_order = ['up', 'down'] algo_ops = {'Scan': {'len' :4, 'ops': [['up', 'w0'], ['r0'], ['down', 'w1'], ['r1']]}, 'Scan Complement': {'len': 4, 'ops': [['up', 'w0'], ['complement', 'r0'], ['bin x', 'down', 'w1'], ['complement', 'r1']]}, 'Scan+': {'len': 8, 'ops': [['up', 'w0'], ['r0'], ['down', 'w1'], ['r1'], ['w0'], ['r0'], ['up', 'w1'], ['r1']]}, 'MATS+': {'len': 5, 'ops': [['up', 'w0'], ['r0', 'w1'], ['down', 'r1', 'w0']]}, 'March C-': {'len': 10, 'ops': [['up', 'w0'], ['r0', 'w1'], ['r1', 'w0'], ['down', 'r0', 'w1'], ['r1', 'w0'], ['r0']]}, 'PMOVI': {'len': 13, 'ops': [['down', 'w0'], ['up', 'r0', 'w1', 'r1'], ['r1', 'w0', 'r0'], ['down', 'r0', 'w1', 'r1'], ['r1', 'w0', 'r0']]}, 'March U': {'len': 13, 'ops': [['up', 'w0'], ['r0', 'w1', 'r1', 'w0'], ['r0', 'w1'], ['down', 'r1', 'w0', 'r0', 'w1'], ['r1', 'w0']]}, 'March LR': {'len': 14, 'ops': [['up', 'w0'], ['down', 'r0', 'w1'], ['up', 'r1', 'w0', 'r0', 'w1'], ['r1', 'w0'], ['r0', 'w1', 'r1', 'w0'], ['r0']]}, # 'BLIF = 10n': # TODO implement next command # 'HAMW8 = 41n': # TODO implement hammer command 'BLIF-': {'len': 8, 'ops': [['up', 'w0'], ['w1', 'r1', 'w0'], ['w1'], ['w0', 'r0', 'w1']]}, 'Algor. B': {'len': 17, 'ops': [['up', 'w0'], ['r0', 'w1', 'w0', 'w1'], ['r1', 'w0', 'r0', 'w1'], ['down', 'r1', 'w0', 'w1', 'w0'], ['r0', 'w1', 'r1']]}, # 'HAMR8 = 18n': # TODO implement hammer command }
[docs] def __init__(self, parent, bitwidth, start, end, readfunc, writefunc, beforefunc=None, afterfunc=None): self.parent = parent self.start = start self.end = end self.readfunc = readfunc self.writefunc = writefunc self.beforefunc = beforefunc self.afterfunc = afterfunc self._ao = 'up' # address order self._cm = 'bin x' # counting method posible values = self.pcount_method self._db = 'sdb' # data background = self.pdata_background self.bitwidth = bitwidth self.mer = MER(self.parent.log_error) self.error_values = []
[docs] def algorithmen(self, alist): """ Run the algorithms defined in algo_ops, if alist is a list of algorithm names. If alist is not a list, log an error and return 1. eg. algorithmen(['Scan', 'March C-']) will run the Scan and March C- algorithms defined in algo_ops. If an algorithm name in alist is not defined in algo_ops, log an error and return 1. Parameters ---------- alist : list A list of algorithm names to run, must be keys in algo_ops. Returns ------- errors : int The number of errors found during the algorithms, or 1 if there was an error in the input. """ if type(alist) != list: self.parent.log_error(f'Argument have to be a list. Values are {list(self.algo_ops.keys())}') return 1 errors = 0 for name in alist: if name not in self.algo_ops.keys(): self.parent.log_error(f'Name from algorithmus not availabe, values are {list(self.algo_ops.keys())}') errors += 1 else: errors += self.algorithmus(f"{name} = {self.algo_ops[name]['len']}n", self.algo_ops[name]['ops']) return errors
[docs] def algorithmus(self, name, operations): """ Run a single algorithm defined by the operations list, which is a list of lists of operations to perform. eg. algorithmus('Scan', [['up', 'w0'], ['r0'], ['down', 'w1'], ['r1']]) will run the Scan algorithm defined in algo_ops. Parameters ---------- name : str The name of the algorithm to run. operations : list A list of lists of operations to perform. Returns ------- errors : int The number of errors found during the algorithm. """ starttime = time() self.parent.log_info(f'Memory-Test: will run {name}') errors = 0 # set default values self._ao = 'up' # address order self._cm = 'bin x' # counting method posible values = self.pcount_method self._db = 'sdb' # data background = self.pdata_background for operation in operations: mer_index = 1 for sop in operation: if sop in self.padr_order: self.adr_order = sop elif sop in self.pcount_method: self._cm = sop elif sop in self.pdata_background: self._db = sop elif sop in self.mer.pmer: self.mer[mer_index] = sop mer_index += 1 else: self.parent.log_error(f' Memory_test operation {sop} not valid') errors += self.run() msg = f" Memory_test result: {name} run in {time()-starttime:.2f}s, found {errors} Errors" if errors != 0: self.parent.log_error(msg) else: self.parent.log_info(msg) return errors
[docs] def run(self, start=None, end=None): """ Read/Write memory with the commands in the march element registers. eg. run() will run the memory test with the start and end addresses defined in the constructor, and the operations defined in the MER. Parameters ---------- start : int, optional The start address for the memory test, if not provided, the start address defined in the constructor will be used. end : int, optional The end address for the memory test, if not provided, the end address defined in the constructor will be used. Returns ------- errors : int The number of errors found during the memory test. """ if self.beforefunc is not None: self.beforefunc() start = start if start is not None else self.start end = end if end is not None else self.end self._inc = 1 if self.adr_order == 'up' or self._cm == 'complement' else -1 self._startadr = start if self.adr_order == 'up' or self._cm == 'complement' else end self._endadr = end + self._inc if self.adr_order == 'up' or self._cm == 'complement' else start+self._inc self._space = abs(self._endadr - self._startadr) space_bits = int(math.log2(self._space)) self.error_values = [] counting = f"{self.adr_order:10s}" if not self._cm == 'complement' else "complement" self.parent.log_info(f' Memory_test: from 0x{self._startadr:0x} to 0x{self._endadr-self._inc:0x} {counting} = {self._space} values do {self.mer._values}') adr = self._startadr compare_data = [[] for _ in range(self.mer.SIZE)] errors = 0 docomp = False dump = [] for cnt in range(self._space): mer_cnt = 1 while mer_cnt != self.mer.cnt+1: operand, dat = self.mer[mer_cnt].split(' ') cdata = int(f"{dat*self.bitwidth}", 2) # data to write, or target read data data = getattr(self, operand)(adr, cdata) # call read/write if operand == 'read': compare_data[mer_cnt-1].append([adr, cdata]) dump.append(data) mer_cnt += 1 if self._cm == 'complement' and not docomp: docomp = True lastadr = adr adr = self._startadr + complement(adr - self._startadr, space_bits) elif docomp: docomp = False adr = lastadr + self._inc else: adr += self._inc if self.afterfunc is not None: dump = self.afterfunc() # get list from all read (=captured) values, compare_data_length = sum(len(row) for row in compare_data) if len(dump) != compare_data_length: self.parent.log_error(f' Memory_test: dump error, read {len(dump)} values, should be {compare_data_length}') return 1 len_compare_data = [len(adata) for adata in compare_data] cntarrays = len(compare_data) - len_compare_data.count(0) pdump_start = 0 read_data = compare_data.copy() for length, index in zip(len_compare_data, range(len(len_compare_data))): if length > 0: pdump = dump[pdump_start::cntarrays] # assign the result to the correct operation (=array) pdump_start += 1 read_data[index] = [[x[0], y] for x, y in zip(read_data[index], pdump)] read_data[index].sort(key=lambda x: x[0]) compare_data[index].sort(key=lambda x: x[0]) mer_error = 0 if read_data[index] != compare_data[index]: for rd, cd in zip(read_data[index], compare_data[index]): if rd[1] != cd[1]: mer_error += 1 self.parent.log_error(f" read error 0x{rd[0]:0x} = 0x{rd[1]:0x} != target 0x{cd[1]:0x}") self.parent.log_error(f"Memorytest '{self.mer[index+1]}' found {mer_error} errors") errors += mer_error return errors
[docs] def read(self, adr, compare=None): """ Read data from the memory at the given address and compare it to the provided value if compare is not None. eg. read(0x1000, 0xFF) will read data from address 0x1000 and compare it to 0xFF, logging an error if they do not match. read(0x1000, 0xFF) will read data from address 0x1000 and compare it to 0xFF, logging an error if they do not match. read(0x1000) will read data from address 0x1000 without comparing it to any value. Parameters ---------- adr : int The address to read from. compare : int, optional The value to compare the read data to, if not provided, no comparison will be made. Returns ------- data : int The data read from the memory at the given address. """ dat = self.readfunc(adr) return dat
[docs] def write(self, adr, dat): """ Write data to the memory at the given address. eg. write(0x1000, 0xFF) will write the value 0xFF to address 0x1000. Parameters ---------- adr : int The address to write to. dat : int The data to write to the memory at the given address. Returns ------- None """ self.writefunc(adr, dat)
@property def adr_order(self): """ Address Order, up or down. """ return self._ao @adr_order.setter def adr_order(self, value): if value not in self.padr_order: self.parent.log_error(f"Memory_test: wrong address order: {value} should be {self.padr_order}") return self._ao = value @property def count_method(self): """ Counting method. """ return self._cm @count_method.setter def count_method(self, value): if type(value) is not str and value not in self.pcount_method: self.parent.log_error(f"Memory_test: wrong counter: {value} should be {self.pcount_method}") return self._cm = value @property def data_background(self): """ Data background. 'sdb' - solid DB - all bits with same data 'bdb' - checkerboard DB - adjacent cells with different data 'rdb' - row stripes DB 'cdb' - column striped DB """ return self._ao @data_background.setter def data_background(self, value): if value not in self.pdata_background: self.parent.log_error(f"Memory_test: data_background: {value} should be {self.pdata_background}") return self._db = value