Chapter 2 - Numbers¶
Introduction¶
- For ASIC/SoC engineers, an important skill to learn in Python is to munge numbers, i.e., manipulate bits and convert between different formats such as
hex
,binary
andint
. - Additionally, SystemVerilog has a rich set of operations for randomization. Learning equivalent functions in Python is essential for our use-cases.
We will examine these topics in this chapter.
Practice
You have 3 options to try out the examples in this tutorial
- Enter
python3
on your Linux or MacOS terminal to launch the Python shell - Using this Python web interpreter.
- Using this Google colab notebook
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 fromregisters.py
, such asCONFIG_ADDRESS
are brought into thedv_config.py
namespace. - 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 calledconfig
which has two fieldsmode
andcount
.dv_config.sv
: A testbench class where theconfig
register is accessed within two tasksset_mode_cfg()
andget_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 haveregisters.py
anddv_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()
andget_field()
, to take care of the bit manipulation. set_mode_cfg()
andget_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) |
math.ceil(math.log(n,2)) |
In SystemVerilog $clog2(DEPTH) is frequently used to calculate the address width. It returns ceil of the log base 2 of the argument. Note that Python's n.bit_length() is NOT equivalent to $clog2(n) . |
$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) |
Practice, Practice, Practice¶
That's it for this chapter.
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.
Read this next¶
Chapter 1: Lists and dictionaries Table of contents Chapter 3: Strings