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