from __future__ import annotations # for compatibility with python 3.7-3.9
"""
**svtmp** is a collection of small SystemVerilog templates to ease the automatic
generation of SystemVerilog code.
=============
Dependencies
=============
None. The module is standalone (other than Python standard library)
Python version required: *3.7+*
=============
Installation
=============
Simple::
pip install svtmp
=============
The module
=============
**svtmp** is a collection of small SystemVerilog templates to ease the automatic
generation of SystemVerilog code. **svtmp** templating functions compose with each
other in terms in terms of indentation, so that templating code doesn't have to
care about indents.
**svtmp** support templating functions for the following constructs:
* case statements: :meth:`case`
* case statement items: :meth:`citem`
* always_ff blocks: :meth:`always_ff`
* always_comb blcoks: :meth:`always_comb`
* continuous assignments: :meth:`assign`
* blocking/non-blocking assignemnts :meth:`eq`
* struct typedef definition: :meth:`struct`
* if-else blocks: :meth:`ifelse`
* if block: :meth:`If`
* file headers: :meth:`header`
* comments: :meth:`comment`
* comment header: :meth:`cheader`
* parameters, localparams, constants: :meth:`parameter` :meth:`localparam`, :meth:`const`
* conversion from (string) ints to SV binary/hex literals: :meth:`ui2b`, :meth:`sui2b`, :meth:`ui2h`, :meth:`sui2h`
* preprocessor directives: :meth:`ifdef` :meth:`ifndef` :meth:`define` :meth:`endif`
* import directives: :meth:`Import`
* signal declarations: :meth:`logic`, :meth:`logvec`, :meth:`decl`
* I/O definitions for module ports: :meth:`Input`, :meth:`invec`, :meth:`Output`, :meth:`outvec`, :meth:`inputs`, :meth:`outputs`
* module definitions: :meth:`module`
* package definitions: :meth:`package`
In addition to templating functions, **svtmp** provides a convenience class, :class:`SVTxt` that allows easy
wrapping of templated code into modules/packages/include segments and writing them into .sv/.svh files together with
headers.
===============
svtmp Examples
===============
* Flip Flop. The following code::
from svtmp import * # import the module
t = SVTxt() # create a SystemVerilog text instance
# I/Os for the SV module
ios = [Input(s) for s in 'clk_i','reset_n_i','di'] + [Output('do')]
# add an always_ff block to the SV text instance
t.add(always_ff(rbody = eq('do',ui2b(0,1)), body = eq('do','di')))
# wrap SV text contents in a module with the previously defined I/Os
t.to_module('dff', ios = ios)
# write into a file with header
t.to_sv_file('dff', desc = 'Flip Flop implementation', prj = 'svtmp')
results in a file ``dff.sv`` with these contents:::
/*------------------------------------------------------------------------------
| Title : dff
| Project : svtmp
+------------------------------------------------------------------------------
| Automatically generated with svtmp python library
|
+------------------------------------------------------------------------------
| Description:
| Flip Flop implementation
+------------------------------------------------------------------------------
| File : dff.sv
| Language : SystemVerilog
| Created : 2022-10-23
+------------------------------------------------------------------------------
| Copyright (c) Infineon Technologies AG 2022 - Confidential
+------------------------------------------------------------------------------
*/
module dff
( input logic clk_i,
input logic reset_n_i,
input logic di,
output logic do
);
always_ff @(posedge clk_i,negedge reset_n_i)
begin
if (!reset_n_i)
do <= 1'b0;
else
do <= di;
end
endmodule
* Register thermometer encoder. The following code::
from svtmp import * # import all templates from svtmp
IW = 4 # selection input sel_i width
OW = 2**IW # thermometer output th_o width
t = SVTxt() #SV text instance
#I/Os of the module
ios = inputs(['clk_i','reset_n_i']) + [invec('sel_i',IW-1,0), outvec('th_o',OW-1,0)]
# thermometer output case items
case_items = [citem(cond = ui2h(i, IW),
body = eq('th_o', ui2b((2**(i+1))-1, OW))) for i in range(OW)]
# always_ff block containing the case for thermometer encoding
t.add(always_ff(eq('th_o', ui2h(0, OW)), case('sel_i', case_items)))
# wrap the SVTxt contents into a module
t.to_module('th_enc', ios = ios)
#write to file
t.to_sv_file('th_enc', desc = 'thermometer encoder', prj = 'svtmp')
results in a ``th_enc.sv`` file containing the following::
/*------------------------------------------------------------------------------
| Title : th_enc
| Project : svtmp
+------------------------------------------------------------------------------
| Automatically generated with svtmp python library
|
+------------------------------------------------------------------------------
| Description:
| thermometer encoder
+------------------------------------------------------------------------------
| File : th_enc.sv
| Language : SystemVerilog
| Created : 2022-10-23
+------------------------------------------------------------------------------
| Copyright (c) Infineon Technologies AG 2022 - Confidential
+------------------------------------------------------------------------------
*/
module th_enc
( input logic clk_i,
input logic reset_n_i,
input logic [3:0] sel_i,
output logic [15:0] th_o
);
always_ff @(posedge clk_i,negedge reset_n_i)
begin
if (!reset_n_i)
th_o <= 16'h0000;
else
begin
case(sel_i)
4'h0: th_o <= 16'b0000000000000001;
4'h1: th_o <= 16'b0000000000000011;
4'h2: th_o <= 16'b0000000000000111;
4'h3: th_o <= 16'b0000000000001111;
4'h4: th_o <= 16'b0000000000011111;
4'h5: th_o <= 16'b0000000000111111;
4'h6: th_o <= 16'b0000000001111111;
4'h7: th_o <= 16'b0000000011111111;
4'h8: th_o <= 16'b0000000111111111;
4'h9: th_o <= 16'b0000001111111111;
4'ha: th_o <= 16'b0000011111111111;
4'hb: th_o <= 16'b0000111111111111;
4'hc: th_o <= 16'b0001111111111111;
4'hd: th_o <= 16'b0011111111111111;
4'he: th_o <= 16'b0111111111111111;
4'hf: th_o <= 16'b1111111111111111;
endcase
end
end
endmodule
===============
Module contents
===============
"""
##################################################################
# indentation level: change to meet your site coding style
SVTMP_INDENTATION_WIDTH = 3
""" indentation width in spaces for all templates in ``svtmp``."""
##################################################################
import logging as log
from typing import List
from datetime import date
import os
INDENT = SVTMP_INDENTATION_WIDTH * ' '
""" default indentation for all templates in svtmp. ``INDENT = SVTMP_INDENTATION_WIDTH * ' '``"""
[docs]def ui2b(x: int, nbits: int) -> str:
"""converts unsigned integers to SystemVerilog binary literals.
Example::
>>> ui2b(63, 8)
"8'b00111111"
Arguments:
x : integer input (assumed unsigned).
nbits : number of bits of the SystemVerilog literal.
Returns:
a string with the SystemVerilog binary literal.
"""
if x > (2**nbits - 1) or nbits == 0:
raise ValueError(f'{x} cannot be represented in {nbits} bits')
return f"{nbits}'b{x:0{nbits}b}"
[docs]def sui2b(x: str, nbits: int) -> str:
"""converts unsigned integer strings to SystemVerilog binary literals.
Example::
>>> sui2b('63', 8)
"8'b00111111"
Arguments:
x : integer input (assumed unsigned).
nbits : number of bits of the SystemVerilog literal.
Returns:
a string with the SystemVerilog binary literal.
"""
return ui2b(int(x), nbits)
[docs]def ui2h(x: int, nbits: int) -> str:
"""converts unsigned integers to SystemVerilog hex literals.
Example::
>>> ui2h(63,23)
"23'h00003f"
Arguments:
x : integer input (assumed unsigned).
nbits : number of bits of the SystemVerilog literal.
Returns:
a string with the SystemVerilog binary literal.
"""
if x > (2**nbits - 1) or nbits == 0:
raise ValueError(f'{x} cannot be represented in {nbits} bits')
nhex_digits = (nbits // 4) if nbits % 4 == 0 else (nbits // 4) + 1
number = f"{x:0{nhex_digits}x}".upper()
return f"{nbits}'h{number}"
[docs]def sui2h(x: str, nbits: int) -> str:
"""converts unsigned integer stings to SystemVerilog hex literals.
Example::
>>> ui2h('63',23)
"23'h00003f"
Arguments:
x : integer input (assumed unsigned).
nbits : number of bits of the SystemVerilog literal.
Returns:
a string with the SystemVerilog binary literal.
"""
return ui2h(int(x), nbits)
[docs]def invec(name:str, lhs: int | str, rhs: int | str, typ = 'logic') -> str:
""" generates a packed vector input port. Used to be included in a list of ports
and passed as argument to :meth:`module` or :meth:`SVTxt.to_module`
Example::
>> invec('data_i', 7, 0)
'input logic [7:0] data_i'
>> invec('sfr_i', 3, 0, typ = 'sfr_in')
'input sfr_in [3:0] sfr_i'
Arguments:
name : input name
lhs : left-hand-side of the vector width definition (typ. MSB)
rhs : right-hand-side of the vector width definition (typ. LSB)
typ : port type
Returns: a string with the port definition
"""
if name == '':
raise ValueError('a port requires a non-empty string name')
return f'input {typ} [{lhs}:{rhs}] {name}'
[docs]def Output(name:str,typ:str = 'logic') -> str:
""" generates a single output port. Used to be included in a list of ports
and passed as argument to :meth:`module` or :meth:`SVTxt.to_module`.
Examples::
>> Output('clk_i')
'output logic clk_i'
>> Output('reset_i', 'reset_t')
'output reset_t reset_i'
Arguments:
name : output port name
typ : output port type
Returns: a string with the port definition
"""
if name == '':
raise ValueError('a port requires a non-empty string name')
return f'output {typ} {name}'
[docs]def outvec(name:str, lhs:int | str, rhs:int | str, typ = 'logic') -> str:
""" generates a packed vector output port. Used to be included in a list of ports
and passed as argument to :meth:`module` or :meth:`SVTxt.to_module`
Example::
>> outvec('data_o', 7, 0)
'output logic [7:0] data_o'
>> outvec('sfr_o', 3, 0, typ = 'sfr_out')
'output sfr_out [3:0] sfr_o'
Arguments:
name : output name
lhs : left-hand-side of the vector width definition (typ. MSB)
rhs : right-hand-side of the vector width definition (typ. LSB)
typ : port type
Returns: a string with the port definition
"""
if name == '':
raise ValueError('a port requires a non-empty string name')
return f'output {typ} [{lhs}:{rhs}] {name}'
[docs]def outputs(outs: List[str]) -> List[str]:
""" short-hand function to generate a list of logic single outputs from
a list of string names.
Example::
>> outputs(['clk_i', 'reset_n_i', 'enable_i'])
['output logic clk_i', 'output logic reset_n_i', 'output logic enable_i']
Arguments:
outs : list of names for the output signals
Returns: a list of strings with the outputs definition
"""
return list([Output(o) for o in outs])
[docs]def struct(typ : str, decls : str | List[str], packed : bool = True, debug : bool = False):
""" generates SystemVerilog struc type definition.
Example::
>> decls = [logic('en'), logic('dis'), logvec('cnt', 7,0)]
>> print(struct(typ = 'sfr_cnt_t', decls = decls))
typedef struct packed {
logic en;
logic dis;
logic [7:0] cnt;
} sfr_cnt_t;
Arguments:
typ: name of the struc type
decls: newline-separated string with signal declarations, or list of strings with them
Returns: a string with the typedef struct definition
"""
if decls == [] or decls == '':
raise ValueError('struct definition cannot be empty')
pk = 'packed ' if packed else ''
ind_body = indent(_ljoin(decls))
s_struct = f'typedef struct {pk}{{\n{ind_body}\n}} {typ};'
if debug:
log.debug(f'SVTMP - struct: {s_struct}')
return s_struct
[docs]def package(name: str, body: str | List[str], debug : bool = False):
""" generates a SystemVerilog package.
Example::
>> lps = [localparam(c[0],c[1]) for c in (('DATAWIDTH', 16), ('ADDRWIDTH', 10) ,('OFFSET', 100))]
>> print(package(name = 'data_pkg', body = lps))
package data_pkg;
localparam DATAWIDTH = 16 ;
localparam ADDRWIDTH = 10 ;
localparam OFFSET = 100 ;
endpackage: data_pkg
Arguments:
name : name of the package.
body : string or list of strings containing the body of the package.
Returns: a string with the complete SV package.
"""
ibody = indent(_ljoin(body))
s_pack = f'package {name};\n{ibody}\nendpackage: {name}'
if debug:
log.debug('SVTMP - package: \n{s_pack}')
return s_pack
[docs]def assign(lhs: str,rhs: str, debug: bool = False):
""" generates an continuous assignment statement.
Example::
>> assign('a', ui2h(64,16))
"assign a = 16'h0040;"
Arguments:
lhs : left-hand side of the assigment.
rhs : right hand side of the assignment.
Returns: a string with the assign statement.
"""
s_assign = f'assign {lhs} = {rhs};'
if debug:
log.debug('SVTMP - assign: {s_assign}')
return s_assign
[docs]def const(typ: str, lhs: str,rhs: str, cmt: str = '', debug: bool = False):
""" generates a constant declaration.
Example::
>> const(typ = 'real', lhs = 'T', rhs = 25.0 , cmt = 'this is a comment')
'const real T = 25.0; // this is a comment;'
Arguments:
typ : type of the constant
lhs : constant name
rhs : constant value
cmt : comment after constant
Returns: a constant declaration.
"""
s_const = f'const {typ} {lhs} = {rhs}; {comment(cmt)};'
if debug:
log.debug(f'SVTMP - constant declaration: {s_const}')
return s_const
[docs]def parameter(lhs: str,rhs: str , mod_decl: bool = True, debug: bool = False):
s_parameter = f'parameter {lhs} = {rhs};'
if debug:
log.debug(f'SVTMP - parameter declaration: {s_parameter}')
return s_parameter
[docs]def localparam(lhs: str,rhs: str, cmt: str = '', debug: bool = False):
s_localparam = f'localparam {lhs} = {rhs} {comment(cmt)};'
if debug:
log.debug(f'SVTMP - localparam declaration: {s_localparam}')
return s_localparam
[docs]def ifdef(s: str) -> str:
return f'`ifdef {s}'
[docs]def ifndef(s: str) -> str:
return f'`ifndef {s}'
[docs]def define(s: str) -> str:
return f'`define {s}'
[docs]def endif() -> str:
return f'`endif'
[docs]def Import(pkg: str) -> str:
return f'import {pkg}::*;'
[docs]def logvec(name, lsb, rsb,cmt = '', debug: bool = False):
if name == '':
raise ValueError('a declaration requires a non-empty string name')
if cmt == '':
s_logvec = f'logic [{lsb}:{rsb}] {name};'
else:
s_logvec = f'logic [{lsb}:{rsb}] {name}; {comment(cmt)}'
if debug:
log.debug(f'SVTMP - logic vector declaration: {s_logvec}')
return s_logvec
[docs]def logic(name: str, cmt: str = '', debug: bool = False):
if name == '':
raise ValueError('a declaration requires a non-empty string name')
if cmt == '':
s_logic = f'logic {name};'
else:
s_logic = f'logic {name}; {comment(cmt)}'
if debug:
log.debug(f'SVTMP - logic declaration: {s_logic}')
return s_logic
[docs]def decl(typ: str, name: str, cmt: str = '', debug: bool = False):
if name == '':
raise ValueError('a declaration requires a non-empty string name')
if cmt == '':
s_decl = f'{typ} {name};'
else:
s_decl = f'{typ} {name}; {comment(cmt)}'
if debug:
log.debug(f'SVTMP - signal declaration: {s_decl}')
return s_decl
[docs]def eq(lhs,rhs, block = False, debug: bool = False):
op = '=' if block else '<='
s_assignment = f'{lhs} {op} {rhs};'
if debug:
log.debug(f'SVTMP - assignment: {s_assignment}')
return s_assignment
[docs]def concat(items: List[str], debug: bool = False):
s_concat = f'{{{", ".join(items)}}}'
if debug:
log.debug(f'SVTMP - concatenation: {s_concat}')
return s_concat
[docs]def If(cond: str, body: str | List[str], debug: bool = False):
s_if = f'if ({cond})\n{block(body)}'
if debug:
log.debug(f'SVTMP - if-block:\n {s_if}')
return s_if
[docs]def ifelse(cond: str, tbody: str | List[str], fbody: str | List[str], debug: bool = False):
s = f'if ({cond})\n'
s += block(tbody)
s += f'\nelse\n'
s += block(fbody)
if debug:
log.debug(f'SVTMP - if-else-block:\n {s}')
return s
[docs]def always_comb(body: str | List[str], debug: bool = False):
s_always_comb = f'always_comb\n{block(body)}'
if debug:
log.debug(f'SVTMP - always_comb block:\n {s_always_comb}')
return s_always_comb
[docs]def make_always_ff(clk = 'clk_i', reset = 'reset_n_i',elevel = True, rlevel = False):
def aff(rbody, body):
return always_ff(body, rbody, clk, reset, elevel, rlevel)
return aff
[docs]def always_ff(rbody: str | List[str],
body: str | List[str],
clk: str = 'clk_i',
reset: str = 'reset_n_i',
elevel: bool = True,
rlevel: bool = False,
debug: bool = False):
cedge = 'posedge' if elevel else 'negedge'
redge = 'posedge' if rlevel else 'negedge'
first = f'always_ff @({cedge} {clk},{redge} {reset})\nbegin\n'
rcond = f'{reset}' if rlevel else f'!{reset}'
aff_body = ifelse(rcond, rbody, body)
s_always_ff = first + indent(aff_body) + '\nend'
if debug:
log.debug(f'SVTMP - always_ff block:\n {s_always_ff}')
return s_always_ff
[docs]def case_item(cond, body):
return f'{cond}: {block(body)}'
[docs]def citem(cond, body):
return f'{cond}: {block(body)}'
[docs]def case(key: str, body: str | List[str]):
return f'case({key})\n{indent(body)}\nendcase\n'
[docs]def module(name:str, body: List[str] | str,
ios: List[str] | str | None = None,
parameters: List[str] | str | None = None,
imports: List[str] | str | None = None) -> str:
s = f'module {name}\n' if ios else f'module {name}; \n'
if imports:
s += indent(_ljoin(imports))
s += '\n'
if parameters:
s += indent(',\n'.join(parameters),spaces = 5 * ' ', first = ' #( ')
s += "\n )\n"
if ios:
s += indent(',\n'.join(ios),spaces = 4 * ' ', first = ' ( ')
s += '\n );\n\n'
s += indent(_ljoin(body))
s += '\nendmodule\n'
return s
[docs]def indent(fragment: List[str] | str, spaces:str = INDENT, first:str = INDENT) -> str:
""" takes a (potentially) multiline string or a list of strings and indents it
(joining the result by newlines if the input was a list of strings)
Example 1::
>>> s = ['logic a;', 'logic b;', 'logic c;']
>>> print(indent(s, spaces = 4*' ', first = 6*' '))
logic a;
logic b;
logic c;
Example 2::
>> print(indent('a\\nb\\nc\\n')) # with SVTMP_INDENTATION_WIDTH = 3
a
b
c
Arguments:
fragment : string or list of strings to be indented.
spaces : indentation string (normally a number of consecutive spaces) for all lines except for first.
first : indentation string for first line/string in list.
Returns:
a string with indented input (either indented string or newling-concatenated string with list strings indented.
"""
if isinstance(fragment, list):
items = []
for s in fragment:
lines = s.split('\n')
items.extend(lines)
elif isinstance(fragment, str):
items = fragment.split('\n')
indented_items = []
for i,item in enumerate(items):
if i == 0:
indented_items.append(first + item)
else:
indented_items.append(spaces + item)
return '\n'.join(indented_items)
[docs]def block(s: str | List[str]) -> str:
""" takes a newline separated string of commands or a list of string commands, and returns a newline separated string of indented commands, wrapped by begin-end if necessary.
Examples::
>> print(block("a = 1"))
a = 1
>> print(block('a = 1\\nb = 2'))
begin
a = 1
b = 2
end
>> stmts = ['a = 1', 'b = 2']
>> print(block(stmts))
begin
a = 1
b = 2
end
Arguments:
s : a string of newline separated statements or a list of statement strings
Returns:
an indented, possibly wrapped in begin-end string with the input statements
"""
if isinstance(s, str):
if '\n' in s[:-1]: #more than 2 lines
lines = s.split('\n')
return f'begin\n{indent(_ljoin(s))}\nend'
else:
return indent(s)
elif isinstance(s, list):
if len(s) > 1:
return f'begin\n{indent(_ljoin(s))}\nend'
else:
return indent(s[0])
def _ljoin(strs: List[str] | str) -> str:
""" returns the newline-concatenation of a list of strings into a single string.
Example::
>>> _ljoin(['string0', 'string1', 'string2'])
'string0\nstring1\nstring2'
Arguments:
"""
if isinstance(strs, str):
return strs
else:
return '\n'.join(strs)
[docs]class SVTxt(object):
[docs] def __init__(self):
self.txt = ""
[docs] def sep(self, n : int = 1):
self.txt += '\n'*n
[docs] def add(self, f : str | List[str]):
if isinstance(f, str):
self.txt += f + '\n'
else:
self.txt += '\n'.join(f) + '\n'
[docs] def addsp(self, f : str | List[str]):
if isinstance(f, str):
self.txt += f + ('\n'*2)
else:
self.txt += '\n'.join(f) + ('\n'*2)
[docs] def to_module(self, name : str,
ios: List[str] | str | None = None,
parameters: List[str] | str | None = None,
imports: List[str] | str | None = None):
self.txt = module(name, self.txt, ios, parameters, imports)
[docs] def to_package(self, name : str):
self.txt = package(name, self.txt)
[docs] def to_sv_file(self, name : str,
path : str = '.',
desc : str = '',
prj : str | None = None):
fname = name + '.svh'
h = header(name, fname = fname, desc = desc, prj = prj)
try:
with open(os.path.join(path, fname), 'w') as fout:
print(h + '\n' + self.txt, file = fout)
except FileNotFoundError:
log.error(f'SVTMP - cannot find {fname}. Exiting.')
exit(1)
except PermissionError:
log.error(f'SVTMP - permission denied to open {fname}. Exiting.')
exit(1)
[docs] def to_svh_file(self, name : str,
path : str = '.',
desc : str = '',
prj : str | None = None,
noheader: bool = False):
fname = os.path.join(path, name + '.svh')
sguard = '_' + name.upper()+'_SVH_'
if noheader:
h = ''
else:
h = header(name = name, fname = name + '.svh', desc = desc, prj = prj)
self.txt = ifndef(sguard) + '\n' + define(sguard) +'\n\n' + h + '\n' + self.txt + '\n`endif'
try:
with open(fname, 'w') as fout:
print(self.txt, file = fout)
except FileNotFoundError:
log.error(f'SVTMP - cannot find {fname}. Exiting.')
exit(1)
except PermissionError:
log.error(f'SVTMP - permission denied to open {fname}. Exiting.')
exit(1)