summaryrefslogtreecommitdiff
path: root/tb/gfx_shader_bind/testbench/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'tb/gfx_shader_bind/testbench/models.py')
-rw-r--r--tb/gfx_shader_bind/testbench/models.py140
1 files changed, 140 insertions, 0 deletions
diff --git a/tb/gfx_shader_bind/testbench/models.py b/tb/gfx_shader_bind/testbench/models.py
new file mode 100644
index 0000000..928f07b
--- /dev/null
+++ b/tb/gfx_shader_bind/testbench/models.py
@@ -0,0 +1,140 @@
+import random
+
+from cocotb.binary import BinaryValue
+
+from cocotb_coverage.coverage import CoverCross, CoverPoint
+
+from .data import BAD_PC
+from .common import log
+
+class PcTable:
+ def __init__(self):
+ self._pcs = {}
+
+ def __getitem__(self, group):
+ if isinstance(group, BinaryValue):
+ group = group.integer
+
+ return self._pcs.get(group, BAD_PC)
+
+ def __setitem__(self, group, pc):
+ assert (pc & 3) == 0
+ if isinstance(group, BinaryValue):
+ group = group.integer
+
+ self._pcs[group] = pc >> 2
+
+class Memory:
+ def __init__(self, name, *, word_size, words, start=0):
+ word_mask = word_size - 1
+ assert word_size > 0 and (word_mask & word_size) == 0, \
+ f'{word_size} is not a power of two'
+
+ self._data = {}
+ self._dirty = set()
+ self._observed = set()
+
+ self._start = start
+ self._words = words
+ self._word_size = word_size
+ self._subword_mask = word_mask
+
+ self._all_ones = (1 << (8 * word_size)) - 1
+
+ @CoverPoint(
+ f'{name}.read_dirty',
+ bins = [True, False],
+ bins_labels = ['dirty', 'clean'],
+ rel = lambda word_num, dirty: self._test_bin(word_num, self._dirty, dirty),
+ )
+ @CoverPoint(
+ f'{name}.read_observed',
+ bins = [True, False],
+ bins_labels = ['multiple_reads', 'first_read'],
+ rel = lambda word_num, observed: self._test_bin(word_num, self._observed, observed),
+ )
+ @CoverCross(
+ f'{name}.read_dirty_observed',
+ items = [
+ f'{name}.read_dirty',
+ f'{name}.read_observed',
+ ],
+ ign_bins = [
+ ('dirty', 'first_read'),
+ ],
+ )
+ def _read_word(word_num):
+ if self._expect_in_range(word_num):
+ self._observed.add(word_num)
+
+ word = self._data.get(word_num)
+ if word is None:
+ log.warning(f'Uninitialized memory read: {self._addr_repr(word_num)}')
+ word = self._all_ones
+
+ return word
+
+ @CoverPoint(
+ f'{name}.write_dirty',
+ bins = [True, False],
+ bins_labels = ['dirty_write', 'clean_write'],
+ xf = lambda word_num, data: word_num,
+ rel = lambda word_num, dirty: self._test_bin(word_num, self._observed, dirty),
+ )
+ def _write_word(word_num, data):
+ if self._expect_in_range(word_num):
+ self._data[word_num] = data
+ if word_num in self._observed:
+ self._dirty.add(word_num)
+
+ self._read_word = _read_word
+ self._write_word = _write_word
+
+ def read(self, addr):
+ return self._data[addr >> 2]
+
+ def randomize_line(self, addr):
+ first_word = (addr >> 2) & ~15
+ for word_num in range(first_word, first_word + 16):
+ self._write_word(word_num, random.randint(0, self._all_ones))
+
+ def __getitem__(self, index):
+ if not isinstance(index, slice):
+ return super()[index]
+
+ assert index.stop >= index.start
+ assert (index.stop & self._subword_mask) == 0
+ assert (index.start & self._subword_mask) == 0
+
+ return MemoryRead(self, index.start // self._word_size, index.stop // self._word_size)
+
+ def _expect_in_range(self, word_num):
+ delta = word_num - self._start
+
+ in_range = delta >= 0 and delta < self._words
+ if not in_range:
+ log.error(f'Bad memory address: {self._addr_repr(word_num)}')
+
+ return in_range
+
+ def _test_bin(self, word_num, flag_set, flag_bin):
+ if not self._expect_in_range(word_num):
+ return False
+
+ return (word_num in flag_set) == flag_bin
+
+ def _addr_repr(self, word_num):
+ addr = word_num * self._word_size
+ return f'0x{addr:08x}'
+
+class MemoryRead:
+ def __init__(self, mem, start, stop):
+ self._mem, self._start, self._stop = mem, start, stop
+
+ def tobytes(self):
+ array = bytearray()
+ for word_num in range(self._start, self._stop):
+ word = self._mem._read_word(word_num)
+ array.extend(word.to_bytes(self._mem._word_size, 'little'))
+
+ return array