• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Nic30 / pyMathBitPrecise / eab8b13e-e3a5-4f16-a6d9-8128246a03d7

08 Aug 2025 11:32PM UTC coverage: 66.371% (-0.8%) from 67.159%
eab8b13e-e3a5-4f16-a6d9-8128246a03d7

push

circleci

Nic30
feat(Bits3t): get_min_value, get_max_value

217 of 376 branches covered (57.71%)

Branch coverage included in aggregate %.

3 of 13 new or added lines in 1 file covered. (23.08%)

24 existing lines in 1 file now uncovered.

831 of 1203 relevant lines covered (69.08%)

0.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

44.24
/pyMathBitPrecise/bit_utils.py
1
#!/usr/bin/env python3
2
# -*- coding: UTF-8 -*-
3
import math
1✔
4
from typing import List, Tuple, Generator, Union, Optional, Literal, Sequence
1✔
5

6
from pyMathBitPrecise.utils import grouper
1✔
7

8

9
def mask(bits: int) -> int:
1✔
10
    """
11
    Generate mask of specified size (sequence of '1')
12
    """
13
    return (1 << bits) - 1
1✔
14

15

16
def bit_field(_from: int, to: int) -> int:
1✔
17
    """
18
    Generate int which has bits '_from' to 'to' set to '1'
19

20
    :note: _from 0 to 1 -> '1'
21
    """
22
    w = to - _from
1✔
23
    return mask(w) << _from
1✔
24

25

26
def get_bit(val: int, bitNo: int) -> int:
1✔
27
    """
28
    Get bit from int
29
    """
30
    return (val >> bitNo) & 1
1✔
31

32

33
def get_bit_range(val: int, bitsStart: int, bitsLen: int) -> int:
1✔
34
    """
35
    Get sequence of bits from an int value
36
    """
37
    val >>= bitsStart
1✔
38
    return val & mask(bitsLen)
1✔
39

40

41
def clean_bit(val: int, bitNo: int) -> int:
1✔
42
    """
43
    Set a specified bit to '0'
44
    """
45
    return val & ~(1 << bitNo)
1✔
46

47

48
def set_bit(val: int, bitNo: int) -> int:
1✔
49
    """
50
    Set a specified bit to '1'
51
    """
52
    return val | (1 << bitNo)
1✔
53

54

55
def toggle_bit(val: int, bitNo: int) -> int:
1✔
56
    """
57
    Toggle specified bit in int
58
    """
59
    return val ^ (1 << bitNo)
1✔
60

61

62
def set_bit_range(val: int, bitStart: int, bitsLen: int, newBits: int) -> int:
1✔
63
    """
64
    Set specified range of bits in int to a specified value
65
    """
66
    _mask = mask(bitsLen)
1✔
67
    newBits &= _mask
1✔
68

69
    _mask <<= bitStart
1✔
70
    newBits <<= bitStart
1✔
71

72
    return (val & ~_mask) | newBits
1✔
73

74

75
def bit_set_to(val: int, bitNo: int, bitVal: int) -> int:
1✔
76
    """
77
    Set specified bit in int to a specified value
78
    """
79
    if bitVal == 0:
1✔
80
        return clean_bit(val, bitNo)
1✔
81
    elif bitVal == 1:
1!
82
        return set_bit(val, bitNo)
1✔
83
    else:
84
        raise ValueError(("Invalid value of bit to set", bitVal))
×
85

86

87
def byte_mask_to_bit_mask_int(m: int, width: int, byte_width:int=8) -> int:
1✔
88
    """
89
    Expands each bit byte_width times to convert from byte mask to bit mask
90
    """
91
    res = 0
×
92
    mTmp = m
×
93
    byte_mask = mask(byte_width)
×
94
    for i in range(width):
×
95
        b = mTmp & 1
×
96
        if b:
×
97
            res |= byte_mask << (i * byte_width)
×
98
        mTmp >>= 1
×
99

100
    return res
×
101

102

103
def byte_mask_to_bit_mask(m: "Bits3Val", byte_width:int=8) -> "Bits3Val":
1✔
104
    """
105
    Replicate each bit byte_width times
106
    """
107
    res = None
×
108
    for b in m:
×
109
        if res is None:
×
110
            res = b._sext(byte_width)
×
111
        else:
112
            res = b._sext(byte_width)._concat(res)
×
113

114
    return res
×
115

116

117
def bit_mask_to_byte_mask_int(m: int, width: int, byte_width:int=8) -> int:
1✔
118
    """
119
    Compresses all bit in byte to 1 bit to convert from bit mask to byte mask
120
    """
121
    assert width % byte_width == 0
×
122
    mTmp = m
×
123
    res = 0
×
124
    byte_mask = mask(byte_width)
×
125
    for i in range(width // byte_width):
×
126
        B = mTmp & byte_mask
×
127
        if B == byte_mask:
×
128
            res |= 1 << i
×
129
        else:
130
            assert B == 0, "Each byte must be entirely set or entirely unset"
×
131
        mTmp >>= byte_width
×
132

133
    return res
×
134

135

136
def apply_set_and_clear(val: int, set_flag: int, clear_flag: int):
1✔
137
    """
138
    :param val: an input value of the flag(s)
139
    :param set_flag: a mask of bits to set to 1
140
    :param clear_flag: a mask of bits to set to 0
141
    :note: set has higher priority
142

143
    :return: new value of the flag
144
    """
145
    return (val & ~clear_flag) | set_flag
1✔
146

147

148
def apply_write_with_mask(current_data: "Bits3val", new_data: "Bits3val", write_mask: "Bits3val") -> "Bits3val":
1✔
149
    """
150
    :return: an updated value current_data which has bytes defined by write_mask updated from new_data
151
    """
152
    m = byte_mask_to_bit_mask(write_mask)
×
153
    return apply_set_and_clear(current_data, new_data & m, m)
×
154

155

156
def extend_to_width_multiple_of_8(v: "Bits3val") -> "Bits3val":
1✔
157
    """
158
    make width of signal modulo 8 equal to 0
159
    """
160
    w = v._dtype.bit_length()
×
161
    cosest_multiple_of_8 = math.ceil((w // 8) / 8) * 8
×
162
    if cosest_multiple_of_8 == w:
×
163
        return v
×
164
    else:
165
        return v._ext(cosest_multiple_of_8)
×
166

167

168
def align(val: int, lowerBitCntToAlign: int) -> int:
1✔
169
    """
170
    Cut off lower bits to align a int value.
171
    """
172
    val = val >> lowerBitCntToAlign
1✔
173
    return val << lowerBitCntToAlign
1✔
174

175

176
def align_with_known_width(val, width: int, lowerBitCntToAlign: int):
1✔
177
    """
178
    Does same as :func:`~.align` just with the known width of val
179
    """
180
    return val & (mask(width - lowerBitCntToAlign) << lowerBitCntToAlign)
×
181

182

183
def iter_bits(val: int, length: int) -> Generator[Literal[0, 1], None, None]:
1✔
184
    """
185
    Iterate bits in int. LSB first.
186
    """
187
    for _ in range(length):
1✔
188
        yield val & 1
1✔
189
        val >>= 1
1✔
190

191

192
def iter_bits_sequences(val: int, length: int) -> Generator[Tuple[Literal[0, 1], int], None, None]:
1✔
193
    """
194
    Iter tuples (bitVal, number of same bits), lsb first
195
    """
196
    assert length > 0, length
×
197
    assert val >= 0
×
198
    # start of new bit seqence
199
    w = 1
×
200
    valBit = val & 1
×
201
    val >>= 1
×
202
    foundBit = valBit
×
203
    for _ in range(length - 1):
×
204
        # extract single bit from val
205
        valBit = val & 1
×
206
        val >>= 1
×
207
        # check if it fits into current bit sequence
208
        if valBit == foundBit:
×
209
            w += 1
×
210
        else:
211
            # end of sequence of same bits
212
            yield (foundBit, w)
×
213
            foundBit = valBit
×
214
            w = 1
×
215

216
    if w != 0:
×
217
        yield (foundBit, w)
×
218

219

220
def to_signed(val: int, width: int) -> int:
1✔
221
    """
222
    Convert unsigned int to negative int which has same bits set (emulate sign overflow).
223

224
    :note: bits in value are not changed, just python int object
225
        has signed flag set properly. And number is in expected range.
226
    """
227
    if val > 0:
1✔
228
        msb = 1 << (width - 1)
1✔
229
        if val & msb:
1✔
230
            val -= mask(width) + 1
1✔
231
    return val
1✔
232

233

234
def to_unsigned(val, width) -> int:
1✔
235
    if val < 0:
1✔
236
        return val & mask(width)
1✔
237
    else:
238
        return val
1✔
239

240

241
def mask_bytes(val: int, byte_mask: int, mask_bit_length: int) -> int:
1✔
242
    """
243
    Use each bit in byte_mask as a mask for each byte in val.
244

245
    :note: Useful for masking of value for HW interfaces where mask
246
        is represented by a vector of bits where each bit is mask
247
        for byte in data vector.
248
    """
249
    res = 0
1✔
250
    for i, m in enumerate(iter_bits(byte_mask, mask_bit_length)):
1✔
251
        if m:
1✔
252
            res |= (val & 0xff) << (i * 8)
1✔
253
        val >>= 8
1✔
254
    return res
1✔
255

256

257
INT_BASES = {
1✔
258
    "b": 2,
259
    "o": 8,
260
    "d": 10,
261
    "h": 16,
262
}
263

264

265
class ValidityError(ValueError):
1✔
266
    """
267
    Value is not fully defined and thus can not be used
268
    """
269

270

271
def normalize_slice(s: slice, obj_width: int) -> Tuple[int, int]:
1✔
272
    start, stop, step = s.start, s.stop, s.step
1✔
273
    if step is not None and step != -1:
1✔
274
        raise NotImplementedError(s.step)
275
    else:
276
        step = -1
1✔
277
    if stop is None:
1✔
278
        stop = 0
1✔
279
    else:
280
        stop = int(stop)
1✔
281

282
    if start is None:
1✔
283
        start = int(obj_width)
1✔
284
    else:
285
        start = int(start)
1✔
286
    # n...0
287
    if start <= stop:
1✔
288
        raise IndexError(s)
1✔
289
    firstBitNo = stop
1✔
290
    size = start - stop
1✔
291
    if start < 0 or stop < 0 or size < 0 or start > obj_width:
1✔
292
        raise IndexError(s)
1✔
293

294
    return firstBitNo, size
1✔
295

296

297
def reverse_bits(val: int, width: int):
1✔
298
    """
299
    Reverse bits in integer value of specified width
300
    """
301
    v = 0
1✔
302
    for i in range(width):
1✔
303
        v |= (get_bit(val, width - i - 1) << i)
1✔
304
    return v
1✔
305

306

307
def extend_to_size(collection: Sequence, items: int, pad=0):
1✔
308
    toAdd = items - len(collection)
1✔
309
    assert toAdd >= 0
1✔
310
    for _ in range(toAdd):
1✔
311
        collection.append(pad)
1✔
312

313
    return collection
1✔
314

315

316
def rotate_right(v: int, width: int, shAmount:int):
1✔
317
    # https://www.geeksforgeeks.org/rotate-bits-of-an-integer/
318
    assert v >= 0, v
×
319
    assert width > 0, width
×
320
    assert shAmount >= 0, shAmount
×
321
    return (v >> shAmount) | ((v << (width - shAmount)) & mask(width))
×
322

323

324
def rotate_left(v: int, width: int, shAmount:int):
1✔
325
    # https://www.geeksforgeeks.org/rotate-bits-of-an-integer/
326
    assert v >= 0, v
×
327
    assert width > 0, width
×
328
    assert shAmount >= 0, shAmount
×
329
    return ((v << shAmount) & mask(width)) | (v >> (width - shAmount))
×
330

331

332
def bit_list_reversed_endianity(bitList: List[Literal[0, 1]], extend=True):
1✔
333
    w = len(bitList)
1✔
334
    i = w
1✔
335

336
    items = []
1✔
337
    while i > 0:
1✔
338
        # take last 8 bytes or rest
339
        lower = max(i - 8, 0)
1✔
340
        b = bitList[lower:i]
1✔
341
        if extend:
1!
342
            extend_to_size(b, 8)
1✔
343
        items.extend(b)
1✔
344
        i -= 8
1✔
345

346
    return items
1✔
347

348

349
def bit_list_reversed_bits_in_bytes(bitList: List[Literal[0, 1]], extend=None):
1✔
350
    "Byte reflection  (0x0f -> 0xf0)"
351
    w = len(bitList)
1✔
352
    if extend is None:
1!
353
        assert w % 8 == 0
1✔
354
    tmp = []
1✔
355
    for db in grouper(8, bitList, padvalue=0):
1✔
356
        tmp.extend(reversed(db))
1✔
357

358
    if not extend and len(tmp) != w:
1!
359
        rem = w % 8
×
360
        # rm zeros from [0, 0, 0, 0, 0, d[2], d[1], d[0]] like
361
        tmp = tmp[:w - rem] + tmp[-rem:]
×
362

363
    return tmp
1✔
364

365

366
def bytes_to_bit_list_lower_bit_first(bytes_: bytes) -> List[Literal[0, 1]]:
1✔
367
    """
368
    b'\x01' to [1, 0, 0, 0, 0, 0, 0, 0]
369
    """
370
    result: List[Literal[0, 1]] = []
×
371
    for byte in bytes_:
×
372
        for _ in range(8):
×
373
            result.append(byte & 0b1)
×
374
            byte >>= 1
×
375
    return result
×
376

377

378
def bytes_to_bit_list_upper_bit_first(bytes_: bytes) -> List[Literal[0, 1]]:
1✔
379
    """
380
    b'\x01' to [0, 0, 0, 0, 0, 0, 0, 1]
381
    """
382
    result: List[Literal[0, 1]] = []
×
383
    for byte in bytes_:
×
384
        for _ in range(8):
×
385
            result.append((byte & 0x80) >> 7)
×
386
            byte <<= 1
×
387
    return result
×
388

389

390
def byte_list_to_be_int(_bytes: List[Literal[0, 1, 2, 3, 4, 5, 6, 7]]):
1✔
391
    """
392
    In input list LSB first, in result little endian ([1, 0] -> 0x0001)
393
    """
394
    return int_list_to_int(_bytes, 8)
×
395

396

397
def bit_list_to_int(bitList: List[Literal[0, 1]]):
1✔
398
    """
399
    In input list LSB first, in result little endian ([0, 1] -> 0b10)
400
    """
401
    res = 0
1✔
402
    for i, r in enumerate(bitList):
1✔
403
        res |= (r & 0x1) << i
1✔
404
    return res
1✔
405

406

407
def bit_list_to_bytes(bitList: List[Literal[0, 1]]) -> bytes:
1✔
408
    byteCnt = len(bitList) // 8
×
409
    if len(bitList) % 8:
×
410
        byteCnt += 1
×
411
    return bit_list_to_int(bitList).to_bytes(byteCnt, 'big')
×
412

413

414
def int_list_to_int(il: List[int], item_width: int):
1✔
415
    """
416
    [0x0201, 0x0403] -> 0x04030201
417
    """
418
    v = 0
1✔
419
    for i, b in enumerate(il):
1✔
420
        v |= b << (i * item_width)
1✔
421

422
    return v
1✔
423

424

425
def int_to_int_list(v: int, item_width: int, number_of_items: int):
1✔
426
    """
427
    opposite of :func:`~.int_list_to_int`
428
    """
429
    item_mask = mask(item_width)
1✔
430
    res = []
1✔
431
    for _ in range(number_of_items):
1✔
432
        res.append(v & item_mask)
1✔
433
        v >>= item_width
1✔
434

435
    assert v == 0, ("there should be nothing left, the value is larger", v)
1✔
436
    return res
1✔
437

438

439
def reverse_byte_order(val: "Bits3val"):
1✔
440
    """
441
    Reverse byteorder (littleendian/bigendian) of signal or value
442
    """
443
    w = val._dtype.bit_length()
×
444
    i = w
×
445
    items = []
×
446

447
    while i > 0:
×
448
        # take last 8 bytes or rest
449
        lower = max(i - 8, 0)
×
450
        items.append(val[i:lower])
×
451
        i -= 8
×
452

453
    # Concat(*items)
454
    top = None
×
455
    for s in items:
×
456
        if top is None:
×
457
            top = s
×
458
        else:
459
            top = top._concat(s)
×
460
    return top
×
461

462

463
def reverse_byte_order_int(val: int, width: int):
1✔
464
    assert width % 8 == 0, width
×
465
    return int.from_bytes(val.to_bytes(width // 8, "big"), "little")
×
466

467

468
def is_power_of_2(v: Union["Bits3val", int]):
1✔
469
    if isinstance(v, int):
×
470
        assert v > 0
×
471
        return (v != 0) & ((v & (v - 1)) == 0)
×
472
    else:
473
        return (v != 0) & ((v & (v - 1))._eq(0))
×
474

475

476
def next_power_of_2(v: Union["Bits3val", int], width:Optional[int]=None):
1✔
477
    # depend on the fact that v < 2^width
478
    v = v - 1
×
479
    if isinstance(v, int):
×
480
        assert width is not None
×
481
        v = to_unsigned(v, width)
×
482
    else:
483
        width = v._dtype.bit_length()
×
484

485
    i = 1
×
486
    while True:
×
487
        v |= (v >> i)  # 1, 2, 4, 8, 16 for 32b
×
488
        i <<= 1
×
489
        if i > width // 2:
×
490
            break
×
491

492
    v = v + 1
×
493

494
    if isinstance(v, int):
×
495
        v &= mask(width)
×
496

497
    return v
×
498

499

500
def round_up_to_multiple_of(v: int, divider:int):
1✔
501
    """
502
    Round up the v to be the multiple of divider
503
    """
504
    _v = (v // divider) * divider
×
505
    if _v < v:
×
506
        return _v + divider
×
507
    else:
508
        return _v
×
509

510

511
def round_up_to_power_of_2(x: int):
1✔
UNCOV
512
    assert x >= 0, x
×
UNCOV
513
    if x == 0:
×
UNCOV
514
        return 0
×
515
    return int(2 ** math.ceil(math.log2(x)))
×
516

517

518
def ctlz(Val: int, width: int) -> int:
1✔
519
    """
520
    Count leading zeros
521
    """
522
    if Val == 0:
×
523
        return width
×
524

525
    # Bisection method.
526
    ZeroBits = 0
×
UNCOV
527
    if not is_power_of_2(width):
×
528
        # because alg. works only for pow2 width
529
        _w = next_power_of_2(width, 64)
×
530
        paddingBits = _w - width
×
531
        width = _w
×
532
    else:
UNCOV
533
        paddingBits = 0
×
534

535
    Shift = width >> 1
×
536
    while Shift:
×
UNCOV
537
        Tmp = Val >> Shift
×
UNCOV
538
        if Tmp:
×
UNCOV
539
            Val = Tmp
×
540
        else:
541
            ZeroBits |= Shift
×
542
        Shift >>= 1
×
543
    return ZeroBits - paddingBits
×
544

545

546
def _ctpop_u64(v: int) -> int:
1✔
UNCOV
547
    v = v - ((v >> 1) & 0x5555555555555555)
×
UNCOV
548
    v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333)
×
UNCOV
549
    v = (v + (v >> 4)) & 0x0F0F0F0F0F0F0F0F
×
550
    return (v * 0x0101010101010101) >> 56
×
551

552

553
def ctpop(val: int, width: int):
1✔
554
    """
555
    count number of 1 in val (population count)
556
    """
557
    res = 0
×
558
    mask_u64 = mask(64)
×
UNCOV
559
    while True:
×
UNCOV
560
        res += _ctpop_u64(val & mask_u64)
×
UNCOV
561
        width -= 64
×
UNCOV
562
        if width <= 0:
×
UNCOV
563
            break
×
UNCOV
564
        val >>= 64
×
565
    return res
×
566

567

568
def cttz(val: int, width:int):
1✔
569
    """
570
    Count trailing zeros
571
    """
572
    if val == 0:
×
573
        return width
×
574
    if val & 0x1:
×
575
        return 0
×
576

577
    # ctpop method: (x & -x).bit_length() - 1
578
    # Bisection method.
579
    ZeroBits = 0
×
580
    if not is_power_of_2(width):
×
UNCOV
581
        width = next_power_of_2(width, 64)  # because alg. works only for pow2  width
×
582
    Shift = width >> 1
×
583
    Mask = mask(width) >> Shift
×
UNCOV
584
    while Shift:
×
585
        if (val & Mask) == 0:
×
UNCOV
586
            val >>= Shift
×
UNCOV
587
            ZeroBits |= Shift
×
588

UNCOV
589
        Shift >>= 1
×
UNCOV
590
        Mask >>= Shift
×
591

UNCOV
592
    return ZeroBits
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc