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))