Source code for hwtLib.amba.axis_comp.strformat
from typing import Dict, Tuple, List, Union, Optional
from hdlConvertorAst.to.hdlUtils import iter_with_last
from hwt.code import If, Switch, SwitchLogic, Or
from hwt.hdl.statements.statement import HdlStatement
from hwt.hdl.types.bits import HBits
from hwt.hdl.types.bitsCastUtils import fitTo
from hwt.hdl.types.defs import BIT
from hwt.hdl.types.hdlType import HdlType
from hwt.hdl.types.stream import HStream
from hwt.hdl.types.struct import HStruct
from hwt.hwIO import HwIO
from hwt.hwIOs.hwIOStruct import HdlType_to_HwIO
from hwt.hwIOs.utils import addClkRstn, propagateClkRstn
from hwt.hwModule import HwModule
from hwt.hwParam import HwParam
from hwt.math import log2ceil
from hwt.pyUtils.typingFuture import override
from hwt.serializer.mode import serializeParamsUniq
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal
from hwt.synthesizer.typePath import TypePath
from hwtLib.amba.axi4s import Axi4Stream
from hwtLib.logic.binToBcd import BinToBcd
from hwtLib.types.ctypes import uint32_t
[docs]
class Axi4S_strFormatItem():
"""
:ivar member_path: path which specifies the location of interface with the value on input interface
:ivar digits: number of digitsof output formated number (not used for 's' format)
:ivar format_type: is one of following characters.
+------+-------------------------------+
| char | format meaning |
+======+===============================+
| 'd' | decadic number |
+------+-------------------------------+
| 'b' | binary number |
+------+-------------------------------+
| 'o' | octal number |
+------+-------------------------------+
| 'x' | hexadecimal number lowercase |
+------+-------------------------------+
| 'X' | hexadecimal number uppercase |
+------+-------------------------------+
| 's' | string |
+------+-------------------------------+
:ivar str leading_char_fill: character which should be used to fill leading digits if number of digits
is used but the value have less digits
"""
BITS_PER_CHAR = {
'b': 1,
'o': 3,
'd': 4,
'x': 4,
'X': 4,
}
[docs]
def __init__(self, member_path: TypePath,
format_type: str,
digits: int):
assert format_type in ('d', 'b', 'o', 'x', 'X', 's')
self.member_path = member_path
self.format_type = format_type
self.digits = digits
[docs]
def __repr__(self):
return f"<{self.__class__.__name__:s} {self.member_path}, {self.format_type}, {self.digits}>"
[docs]
class HdlType_to_Interface_with_Axi4Stream(HdlType_to_HwIO):
[docs]
def apply(self, dtype: HdlType, field_path: Optional[TypePath]=None) -> HwIO:
"""
Run the conversion
"""
if isinstance(dtype, HStream):
assert dtype.element_t == HBits(8), dtype
assert dtype.start_offsets == (0,), dtype.start_offsets
assert dtype.len_min >= 1, dtype
i = Axi4Stream()
i.DATA_WIDTH = 8
else:
i = super(HdlType_to_Interface_with_Axi4Stream, self).apply(dtype, field_path)
return i
[docs]
@serializeParamsUniq
class Axi4S_strFormat(HwModule):
"""
Generate component which does same thing as printf just in hw.
The output string is stream of encoded characters. The ending '0' is not appended.
And 'last' signal of Axi4Stream is used instead.
:note: use :func:`hwtLib.amba.axis_comp.strformat_fn.axiS_strFormat` to generate instance
of this component from normal string format string and argument
.. hwt-autodoc::
"""
@override
def hwConfig(self):
self.DATA_WIDTH = HwParam(8)
self.FORMAT = HwParam((
"Axi4S_strFormat"
": hex: 0x", Axi4S_strFormatItem(TypePath("data"), 'x', 32 // 4),
", dec: ", Axi4S_strFormatItem(TypePath("data"), 'd', BinToBcd.decadic_deciamls_for_bin(32)),
" is the value of data from example"
))
self.INPUT_T = HwParam(HStruct(
(uint32_t, "data"),
))
self.ENCODING = HwParam("utf-8")
@override
def hwDeclr(self):
addClkRstn(self)
if self.INPUT_T is not None:
# if INPUT_T is none it menas that the component is configured to print a costant string.
self.data_in = HdlType_to_Interface_with_Axi4Stream().apply(self.INPUT_T)
# filter out emplty strings
self.FORMAT = tuple(f for f in self.FORMAT if not isinstance(f, str) or f)
assert self.FORMAT, "Need to have something to print"
with self._hwParamsShared():
self.data_out = Axi4Stream()._m()
[docs]
def build_string_rom(self):
"""
Collect all const strings and char translation tables and pack them in to a content of string rom
"""
# offset of tables for translation to a char
digit_translation_tables = {}
strings = {} # type: Dict[index, bytes]
input_strings = []
max_bcd_digits = 0
max_chars_per_format = 0
for i, f in enumerate(self.FORMAT):
if isinstance(f, str):
s = strings[i] = f.encode(self.ENCODING)
max_chars_per_format = max(max_chars_per_format, len(s))
else:
assert isinstance(f, Axi4S_strFormatItem), f
t = f.format_type
if t == 'd':
# use bcd
max_bcd_digits = max(max_bcd_digits, f.digits)
max_chars_per_format = max(max_chars_per_format, f.digits)
table = [f"{i}" for i in range(10)]
elif t in digit_translation_tables.keys():
continue
elif t == 'b':
table = [f"{i}" for i in range(2)]
elif t == 'o':
table = [f"{i}" for i in range(8)]
elif t == 'x':
table = [f"{i}" for i in range(10)] + ['a', 'b', 'c', 'd', 'e', 'f']
elif t == 'X':
table = [f"{i}" for i in range(10)] + ['A', 'B', 'C', 'D', 'E', 'F']
elif t == 's':
input_strings.append(f)
continue
else:
raise NotImplementedError(f"Not implement for format char:{t}")
max_chars_per_format = max(max_chars_per_format, f.digits)
# we use bytes representation as we need an exact size of the string
digit_translation_tables[t] = "".join(table).encode(self.ENCODING)
_string_rom = []
strings_offset_and_size = {}
# sort based on table inclusion and remove tables which are included in some larger table
# e.g. d (0-9) is included in X (0-F)
last_end = 0
for i, s in sorted(strings.items(), key=lambda x: x[0]):
strings_offset_and_size[i] = (last_end, len(s))
_string_rom.append(s)
last_end += len(s)
for f_char, table in sorted(digit_translation_tables.items(), key=lambda x: x[0]):
strings_offset_and_size[f_char] = (last_end, len(table))
_string_rom.append(table)
last_end += len(table)
_string_rom = b"".join(_string_rom)
return _string_rom, strings_offset_and_size, max_chars_per_format, max_bcd_digits
[docs]
def create_char_mux(self, in_, out_, char_i, digits, bits_per_digit):
"""
Create a MUX which select the degit from input vector.
Also perform necessary type casting for corner cases.
"""
in_w = in_._dtype.bit_length()
Switch(char_i)\
.add_cases([
(digits - i - 1,
out_(
fitTo(
in_[min((i + 1) * bits_per_digit, in_w): i * bits_per_digit],
out_,
# moast significant digits may overlap the input size
extend=True,
)
)
if i * bits_per_digit < in_w else
# case where there are more digits than in the input domain
out_(0)
)
for i in range(digits)])\
.Default(out_(None))
# default branch should not be used as counter should not get to such a value
[docs]
def connect_single_format_group(self,
f_i: int,
f: Union[str, Axi4S_strFormatItem],
strings_offset_and_size: Dict[Union[int, str], Tuple[int, int]],
string_rom: RtlSignal,
char_i: RtlSignal,
to_bcd_inputs: List[Tuple[RtlSignal, HdlStatement]],
en: RtlSignal):
"""
Connect a single formating group or string chunk to output.
Depending on item type this may involve:
* iterate the string characters stored in string_rom
* iterate and translate bin/oct/hex characters
* register bcd input for later connection
* connect an input string from an input Axi4Stream
"""
dout = self.data_out
in_vld = BIT.from_py(1)
res = []
in_last = None
string_rom_index_t = HBits(log2ceil(string_rom._dtype.size), signed=False)
if isinstance(f, str):
str_offset, str_size = strings_offset_and_size[f_i]
str_offset = string_rom_index_t.from_py(str_offset)
res.append(
# read char of the string from string_rom
dout.data(string_rom[str_offset + fitTo(char_i, str_offset, shrink=False)])
)
else:
assert isinstance(f, Axi4S_strFormatItem), f
in_ = self.data_in._fieldsToHwIOs[f.member_path]
if f.format_type in ('d', 'b', 'o', 'x', 'X'):
if f.format_type == 'd':
# first use BCD convertor to convert to BCD
to_bcd_inputs.append((en, in_))
bcd = self.bin_to_bcd.dout
in_vld = bcd.vld
bcd.rd(dout.ready & en & char_i._eq(f.digits - 1))
in_ = bcd.data
bits_per_char = Axi4S_strFormatItem.BITS_PER_CHAR[f.format_type]
actual_digit = self._sig(f"f_{f_i}_actual_digit", HBits(bits_per_char))
to_str_table_offset, _ = strings_offset_and_size[f.format_type]
to_str_table_offset = string_rom_index_t.from_py(to_str_table_offset)
# iterate output digits using char_i
self.create_char_mux(in_, actual_digit, char_i, f.digits, bits_per_char)
res.append(
# use char translation table from string_rom to translate digit in to a char
dout.data(string_rom[to_str_table_offset + fitTo(actual_digit, to_str_table_offset, shrink=False)])
)
str_size = f.digits
else:
# connect a string from an input Axi4Stream
assert f.format_type == 's', f.format_type
assert in_.DATA_WIDTH == 8, in_.DATA_WIDTH
assert in_.USE_STRB == False, in_.USE_STRB
assert in_.USE_KEEP == False, in_.USE_KEEP
in_vld = in_.valid
in_.ready(dout.ready & en)
res.append(
dout.data(in_.data)
)
in_last = in_.last
if in_last is None:
# if signal to detect last character is not overridden use counter to resolve it
in_last = char_i._eq(str_size - 1)
return res, in_vld, in_last,
@override
def hwImpl(self) -> None:
_string_rom, strings_offset_and_size, max_chars_per_format, max_bcd_digits = self.build_string_rom()
if self.DATA_WIDTH != 8:
# it self.DATA_WIDTH != 1B we need to handle all possible alignments and shifts, pre-compute some strings
# because number of string memory ports is limited etc.
raise NotImplementedError()
# instantiate bin_to_bcd if required
if max_bcd_digits > 0:
bin_to_bcd = BinToBcd()
bin_to_bcd.INPUT_WIDTH = log2ceil(10 ** max_bcd_digits - 1)
self.bin_to_bcd = bin_to_bcd
# tuples (cond, input)
to_bcd_inputs = []
string_rom = self._sig("string_rom", HBits(8)[len(_string_rom)], def_val=[int(c) for c in _string_rom])
char_i = self._reg("char_i", HBits(log2ceil(max_chars_per_format), signed=False), def_val=0)
# create an iterator over all characters
element_cnt = len(self.FORMAT)
dout = self.data_out
if element_cnt == 1:
en = 1
f_i = 0
f = self.FORMAT[f_i]
_, out_vld, out_last = self.connect_single_format_group(f_i, f, strings_offset_and_size, string_rom, char_i, to_bcd_inputs, en)
char_i_rst = out_last
else:
main_st = self._reg("main_st", HBits(log2ceil(element_cnt), signed=False), def_val=0)
char_i_rst = out_last = out_vld = BIT.from_py(0)
main_st_fsm = Switch(main_st)
for is_last_f, (f_i, f) in iter_with_last(enumerate(self.FORMAT)):
en = main_st._eq(f_i)
data_drive, in_vld, in_last = self.connect_single_format_group(
f_i, f, strings_offset_and_size, string_rom, char_i,
to_bcd_inputs, en)
# build out vld from all input valids
out_vld = out_vld | (en & in_vld)
# keep only last of the last part
out_last = en & in_last
char_i_rst = char_i_rst | out_last
main_st_fsm.Case(
f_i,
If(dout.ready & in_vld & in_last,
main_st(0)
if is_last_f else
main_st(main_st + 1)
),
*data_drive
)
main_st_fsm.Default(
main_st(None),
dout.data(None)
)
dout.valid(out_vld)
dout.last(out_last)
If(dout.ready & out_vld,
If(char_i_rst,
char_i(0)
).Else(
char_i(char_i + 1)
)
)
if to_bcd_inputs:
in_ = bin_to_bcd.din
SwitchLogic(
# actual value may be smaller, because bcd is shared among
# multiple input formats
[(c, in_.data(fitTo(v, in_.data, shrink=False)))
for c, v in to_bcd_inputs],
default=in_.data(None)
)
in_.vld(char_i._eq(0) & Or(*(c for c, _ in to_bcd_inputs)))
propagateClkRstn(self)
if __name__ == "__main__":
from hwt.synth import to_rtl_str
m = Axi4S_strFormat()
print(to_rtl_str(m))