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 Bits
from hwt.hdl.types.hdlType import HdlType
from hwt.hdl.types.stream import HStream
from hwt.hdl.types.struct import HStruct
from hwt.interfaces.structIntf import HdlType_to_Interface
from hwt.interfaces.utils import addClkRstn, propagateClkRstn
from hwt.math import log2ceil
from hwt.serializer.mode import serializeParamsUniq
from hwt.synthesizer.interface import Interface
from hwt.synthesizer.param import Param
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal
from hwt.synthesizer.typePath import TypePath
from hwt.synthesizer.unit import Unit
from hwt.synthesizer.vectorUtils import fitTo
from hwtLib.amba.axis import AxiStream
from hwtLib.logic.binToBcd import BinToBcd
from hwtLib.types.ctypes import uint32_t
from hwt.hdl.types.defs import BIT


[docs]class AxiS_strFormatItem(): """ :ivar member_path: path which specifies the loacation 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 folloving 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"<AxiS_strFormatItem {self.member_path}, {self.format_type}, {self.digits}>"
[docs]class HdlType_to_Interface_with_AxiStream(HdlType_to_Interface):
[docs] def apply(self, dtype: HdlType, field_path: Optional[TypePath]=None) -> Interface: """ Run the connversion """ if isinstance(dtype, HStream): assert dtype.element_t == Bits(8), dtype assert dtype.start_offsets == (0,), dtype.start_offsets assert dtype.len_min >= 1, dtype i = AxiStream() i.DATA_WIDTH = 8 else: i = super(HdlType_to_Interface_with_AxiStream, self).apply(dtype, field_path) return i
[docs]@serializeParamsUniq class AxiS_strFormat(Unit): """ Generate compomonent 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 AxiStream 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:: """ def _config(self): self.DATA_WIDTH = Param(8) self.FORMAT = Param(( "AxiS_strFormat" ": hex: 0x", AxiS_strFormatItem(TypePath("data"), 'x', 32 // 4), ", dec: ", AxiS_strFormatItem(TypePath("data"), 'd', BinToBcd.decadic_deciamls_for_bin(32)), " is the value of data from example" )) self.INPUT_T = Param(HStruct( (uint32_t, "data"), )) self.ENCODING = Param("utf-8") def _declr(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_AxiStream().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._paramsShared(): self.data_out = AxiStream()._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, AxiS_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, AxiS_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 AxiStream """ dout = self.data_out in_vld = BIT.from_py(1) res = [] in_last = None string_rom_index_t = Bits(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, AxiS_strFormatItem), f in_ = self.data_in._fieldsToInterfaces[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 = AxiS_strFormatItem.BITS_PER_CHAR[f.format_type] actual_digit = self._sig(f"f_{f_i}_actual_digit", Bits(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 AxiStream 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 overriden use counter to resolve it in_last = char_i._eq(str_size - 1) return res, in_vld, in_last,
def _impl(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", Bits(8)[len(_string_rom)], def_val=[int(c) for c in _string_rom]) char_i = self._reg("char_i", Bits(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", Bits(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.synthesizer.utils import to_rtl_str u = AxiS_strFormat() print(to_rtl_str(u))