summaryrefslogtreecommitdiff
path: root/tb/gfx_shader_bind/testbench/models.py
blob: e65b0536ecc906ef9d5018cbce438fe1687298ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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 = {}
        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):
                if word_num in self._observed:
                    dirty = self._dirty.setdefault(word_num, set())
                    dirty.add(self._data.get(word_num, self._all_ones))
                    dirty.add(data)

                self._data[word_num] = data

        self._read_word = _read_word
        self._write_word = _write_word

    def read(self, addr):
        return self._data[addr >> 2]

    def read_cached(self, addr):
        addr = addr >> 2

        data = self._data[addr]
        if dirty := self._dirty.get(addr):
            data = tuple(dirty.union({data}))

        return data

    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