Skip to content

Chapter 2 - Numbers

Goal

  • SystemVerilog has a rich set of functions and operators for numbers. We will explore Python equivalents of these.
  • Let's get straight to business and skip the obvious stuff. Add, subtract, modulo and other operators not mentioned here work the same as SystemVerilog.

Working with hex numbers

Examples below show how to work with hex numbers and convert between hex and int.

# Just like in C, hex numbers are represented with the prefix `0x`
>>> s = 0x1f4
>>> s
500

# To convert an int to hex and store it in the hex format
>>> s = hex(500)
>>> s
'0x1f4'

# Note that using `hex(N)` actually creates a string
>>> s = hex(500)
>>> s
'0x1f4'
>>> type(s)
<class 'str'>

# So you can slice a hex number just like any other string.
# Remove the 0x in front of the hex number.
>>> s[2:]
'1f4'

# Convert a hex "string" to int
# You have to specify the base as the second argument
>>> int('0x1f4', 16)
500

# You'll be happy to know that in Python, just like in SV,
# you can use `_` to make large numbers more readable
>>> 0x0001_0000
65536
>>> 0x0000_0000_DEAD_BEEF
3735928559

Working with binary numbers

Examples below show how to convert between bin and int.

# To create a binary number just like hex, use the bin(N) function
>>> bin(500)
'0b111110100'

# bin(N) also creates a string
# To convert a binary "string" to int
# You have to specify the base as the second argument
>>> int('0b111110100', 2)
500

# Binary numbers can also use _ to make it readable
>>> s = 0b1111_0100_1010_0001
>>> hex(s)
'0xf4a1'

Bit manipulation

Bit manipulation in Python works just like in C. Let's consider the following SystemVerilog snippet where we are trying to set bits [7:4] to 'hA. The code below shows the equivalent operation in Python.

SystemVerilog v/s Python bit manipulation

/* SystemVerilog */
logic [31:0] interrupt;

initial begin
    interrupt = 'hFFFF;
    interrupt[7:4] = 'hA;
end
# Python
>>> interrupt = 0xFFFF
>>> interrupt &= ~(0xF << 4) # Clear [7:4]
>>> interrupt |= (0xA << 4)  # Set [7:4]
>>> hex(interrupt)
'0xffaf'

Real world use case for ASIC/SoC engineers

A fundamental pattern you will see, in SytemVerilog code, is registers (i.e., CSRs) being modified. In the example below, you will see a snippet of code in SystemVerilog and its equivalent implementation in Python.

Take your time with this example and examine the code in SV and Python. Observe the following:

  • Both SV and Py use import statements to bring in packages into the current scope This is how the defines from registers.py, such as CONFIG_ADDRESS , are used in dv_config.py.
  • Notice how Python functions are created using def <METHOD_NAME()>, and how arguments are passed to it. The text between ''' is comments, similar to /* */.

SystemVerilog

We have two files

  • registers.svh: A header file which contains the registers, fields and address defines. This file could be hand-created, but is typically generated from the register specification (IP-XACT, RDL, etc). For this example, we have a register called config which has two fields mode and count.
  • dv_config.sv: A testbench class where the config register is accessed within two tasks set_mode_cfg() and get_count().

SystemVerilog: Setting and getting CSR fields

import registers::*;

...
task set_mode_cfg();
    config_t cfg = 0;
    cfg.mode = 4'h5;
    // reg_wr(`CONFIG_ADDRESS, cfg); // Non-UVM
    regmodel.config.write(cfg, status); // UVM
endtask

task get_count(output logic[15:0] cnt);
    config_t cfg;

    // reg_rd(`CONFIG_ADDRESS, cfg); // Non-UVM
    regmodel.config.read(cfg, status); // UVM
    cnt = cfg.count;
endtask
...
package registers;
    `define CONFIG_ADDRESS 32'h1000_0040
    `define CONFIG_MODE_FLD_WD 4
    `define CONFIG_MODE_FLG_BIT_POS 0
    `define CONFIG_MODE_FLD_RANGE 3:0
    `define CONFIG_COUNT_FLD_WD 16
    `define CONFIG_COUNT_FLD_BIT_POS 4
    `define CONFIG_COUNT_FLD_RANGE 23:4

    struct packed logic [31:0] {
        logic [11:0] reserved;
        logic [15:0] count;
        logic [3:0] mode;
    } config_t;
endpackage

Python

  • Corresponding to the *.sv files above, we have registers.py and dv_config.py. The comments in the code explain each function.
  • Code, such as the one below, is typically used during post-silicon validation (lab bringup) or if you have a Python-based config generation flow in your UVM TB.
  • I define two convenience functions, set_field() and get_field(), to take care of the bit manipulation.
  • set_mode_cfg() and get_count() represent Python equivalents of the SV tasks.

Python: Setting and getting CSR fields

# import the register defines from registers.py
from registers import *

def set_field(reg, fld, pos, len):
    '''
    Utility function to modify a register and return
    updated value

    input args: 
        reg = current register value
        fld = field value
        pos = starting bit position
        len = bit width of the field

    returns:
        updated `reg` value
    '''
    mask = 2**len - 1      # For a 5-bit field this will become 0x1F
    reg &= ~(mask  << pos) # Clear bits associated with the field first
    reg |= (fld << pos)    # Set the bits associate with that field
    return reg

def get_field(reg, pos, len):
    '''
    Utility function to extract a field from a register

    input args:
        reg = register value
        pos = starting bit position
        len = bit width of the field

    returns:
        extracted `field` value
    '''
    mask = 2**len - 1      # For a 5-bit field this will become 0x1F
    fld = (reg  >> pos) & mask
    return fld

def set_mode_cfg():
    '''
    Equivalent to the SystemVerilog task `set_mode_cfg()`
    '''
    cfg = 0
    cfg = set_field(cfg, 0x5, CONFIG_MODE_FLD_BIT_POS, CONFIG_MODE_FLD_WD)
    reg_wr(CONFIG_ADDRESS, cfg)

def get_count():
    '''
    Equivalent to the SystemVerilog task `get_count()`
    '''
    reg_rd(CONFIG_ADDRESS, cfg)
    cnt = get_fld(cfg, CONFIG_COUNT_FLD_BIT_POS, CONFIG_COUNT_FLD_WD)
    return cnt
CONFIG_ADDRESS              = 0x1000_0040
CONFIG_MODE_FLD_WD          = 4
CONFIG_MODE_FLG_BIT_POS     = 0
CONFIG_COUNT_FLD_WD         = 16
CONFIG_COUNT_FLD_BIT_POS    = 4

Random numbers

Python offers rich randomization utilities through its in-built random library. Here is a comparison between common SystemVerilog use cases and Python equivalents.

SystemVerilog Python Comments
$urandom_range(min, max) random.randint(min, max)
random.randrange(min, max+1)
Note that randint(min, max) is equivalent to $urandom_range
while randrange() generates min <= N < max it does not include max
std::randomize(N) random.getrandbits(k) In order to randomize a variable of arbitrary length,
for example logic [47:0] N, in SV we typically use class randomize().
In Python the equivalent is random.getrandbits(k)
where k is the number of bits.

You could also use random.getrandbytes(K) to get a random number
of bytes rather than bits.
array.shuffle() random.shuffle(L) Shuffle the elements of a list
random.choice([1, 2, 3, 4, 5]) Choose one random element from a list
random.choices([1, 2, 3, 4, 5], k=2) Choose k elements from a list WITH repeating of element allowed
random.sample([1, 2, 3, 4, 5], k=2) Choose k elements from a list WITHOUT repeating any element

Let's see these in action.

>>> import random

# Generate a random float: 0.0 <= N < 1.0 
>>> random.random()
0.08263888128836916

# Generate a random int: min <= N < max
>>> random.randrange(0, 101)
92

# Generate a random int: min <= N <= max
>>> random.randint(0, 100)
62

# Select random element from a list
>>> random.choice([1,2,3,4,5])
5

# Select `k` random elements from a list, *WITH repetition* allowed
>>> random.choices([1,2,3,4,5], k=3)
[4, 3, 3]

# Select `k` random elements from a list, *WITHOUT repetition*
>>> random.sample([1,2,3,4,5], k=3)
[5, 2, 4]

# Shuffle list in place
>>> s = [1, 2, 3, 4, 5]
>>> random.shuffle(s)
>>> s
[2, 5, 4, 3, 1]

Division

# Division always returns a float. You don't have to append a `.0`. 
# Like in SV where this has to be 10/5.0 to force output type to be a float.
>>> 10/5
2.0
>>> int(10/5) # force output to be int
2
>> 10//5 # `//` is a short hand for integer division
2

SystemVerilog math functions in Python

Commonly used math functions are available through Python's in-built math library. You will have to import math before using these. This is equivalent to SystemVerilog's import pkg or C's #include <stdio.h>.

SystemVerilog Python Comment
exp(n) math.exp(n) ex where e=2.71...
sqrt(n) math.sqrt(n)
$clog2(n) n = 5000
n.bit_length()
In SystemVerilog $clog2(DEPTH) is frequently used to calculate
the address width. It returns ceil of the log base 2 of the argument.
In Python it can be achieved using the in-built .bit_length() function.
$floor(n) math.floor(n)
$ceil(n) math.ceil(n)
$pow(m, n) m**n
math.pow(m, n)
$sin(n) math.sin(n)
$cos(n) math.cos(n)

Conclusion

That's it for this chapter. You now know how to

  1. Convert between hex, bin and int.
  2. Perform bit manipulations
  3. Generate random numbers

Practice, Practice, Practice

  • The key to learning Python is practice
  • Set 15 minutes on a timer, fire up python3 on your Linux or MacOS terminal (or use this Python web interpreter) and practice some of the code in this chapter.

Back to Table of Contents

Table of contents