""" Generation of common VHDL constructs """ from datetime import datetime from io import TextIOWrapper from typing import Any, Optional, Set, Tuple from b_asic.codegen.vhdl import VHDL_TAB def write_b_asic_vhdl_preamble(f: TextIOWrapper): """ Write a standard BASIC VHDL preamble comment. Parameters ---------- f : :class:`io.TextIOWrapper` The file object to write the header to. """ f.write(f'--\n') f.write(f'-- This code was automatically generated by the B-ASIC toolbox.\n') f.write(f'-- Code generation timestamp: ({datetime.now()})\n') f.write(f'-- URL: https://gitlab.liu.se/da/B-ASIC\n') f.write(f'--\n\n') def write_ieee_header( f: TextIOWrapper, std_logic_1164: bool = True, numeric_std: bool = True, ): """ Write the standard IEEE VHDL use header with includes of std_logic_1164 and numeric_std. Parameters ---------- f : :class:`io.TextIOWrapper` The TextIOWrapper object to write the IEEE header to. std_logic_1164 : bool, default: True Include the std_logic_1164 header. numeric_std : bool, default: True Include the numeric_std header. """ f.write('library ieee;\n') if std_logic_1164: f.write('use ieee.std_logic_1164.all;\n') if numeric_std: f.write('use ieee.numeric_std.all;\n') f.write('\n') def write_signal_decl( f: TextIOWrapper, name: str, type: str, default_value: Optional[str] = None, name_pad: Optional[int] = None, ): """ Create a VHDL signal declaration: :: signal {name} : {type} [:= {default_value}]; Parameters ---------- f : :class:`io.TextIOWrapper` The TextIOWrapper object to write the IEEE header to. name : str Signal name. type : str Signal type. default_value : str, optional An optional default value to the signal. name_pad : int, optional An optional left padding value applied to the name. """ # Spacing of VHDL signals declaration always with a single tab name_pad = 0 if name_pad is None else name_pad f.write(f'{VHDL_TAB}signal {name:<{name_pad}} : {type}') if default_value is not None: f.write(f' := {default_value}') f.write(f';\n') def write_constant_decl( f: TextIOWrapper, name: str, type: str, value: Any, name_pad: Optional[int] = None, type_pad: Optional[int] = None, ): """ Write a VHDL constant declaration with a name, a type and a value. Parameters ---------- f : :class:`io.TextIOWrapper` The TextIOWrapper object to write the constant declaration to. name : str Signal name. type : str Signal type. value : anything convertable to str Default value to the signal. name_pad : int, optional An optional left padding value applied to the name. """ name_pad = 0 if name_pad is None else name_pad f.write(f'{VHDL_TAB}constant {name:<{name_pad}} : {type} := {str(value)};\n') def write_type_decl( f: TextIOWrapper, name: str, alias: str, ): """ Write a VHDL type declaration with a name tied to an alias. Parameters ---------- f : :class:`io.TextIOWrapper` The TextIOWrapper object to write the type declaration to. name : str Type name alias. alias : str The type to tie the new name to. """ f.write(f'{VHDL_TAB}type {name} is {alias};\n') def write_synchronous_process( f: TextIOWrapper, clk: str, body: str, indent: Optional[int] = 0, name: Optional[str] = None, ): """ Write a regular VHDL synchronous process with a single clock object in the sensitivity list triggering a rising edge block by some body of VHDL code. Parameters ---------- f : :class:`io.TextIOWrapper` The TextIOWrapper to write the VHDL code onto. clk : str Name of the clock. body : str Body of the `if rising_edge(clk) then` block. indent : Optional[int] Indent this process block with `indent` columns name : Optional[str] An optional name for the process """ space = '' if indent is None else ' ' * indent write_synchronous_process_prologue(f, clk, indent, name) for line in body.split('\n'): if len(line): f.write(f'{space}{2*VHDL_TAB}{line}\n') write_synchronous_process_epilogue(f, clk, indent, name) def write_synchronous_process_prologue( f: TextIOWrapper, clk: str, indent: Optional[int] = 0, name: Optional[str] = None, ): """ Write only the prologue of a regular VHDL synchronous process with a single clock object in the sensitivity list triggering a rising edge block by some body of VHDL code. This method should almost always guarantely be followed by a write_synchronous_process_epilogue. Parameters ---------- f : :class:`io.TextIOWrapper` The TextIOWrapper to write the VHDL code onto. clk : str Name of the clock. indent : Optional[int] Indent this process block with `indent` columns name : Optional[str] An optional name for the process """ space = '' if indent is None else ' ' * indent if name is not None: f.write(f'{space}{name}: process({clk})\n') else: f.write(f'{space}process({clk})\n') f.write(f'{space}begin\n') f.write(f'{space}{VHDL_TAB}if rising_edge(clk) then\n') def write_synchronous_process_epilogue( f: TextIOWrapper, clk: Optional[str], indent: Optional[int] = 0, name: Optional[str] = None, ): """ Write only the epilogue of a regular VHDL synchronous process with a single clock object in the sensitivity list triggering a rising edge block by some body of VHDL code. This method should almost always guarantely be followed by a write_synchronous_process_epilogue. Parameters ---------- f : :class:`io.TextIOWrapper` The TextIOWrapper to write the VHDL code onto. clk : str Name of the clock. indent : Optional[int] Indent this process block with `indent` columns name : Optional[str] An optional name for the process """ _ = clk space = '' if indent is None else ' ' * indent f.write(f'{space}{VHDL_TAB}end if;\n') f.write(f'{space}end process') if name is not None: f.write(' ' + name) f.write(';\n') def write_synchronous_memory( f: TextIOWrapper, clk: str, read_ports: Set[Tuple[str, str, str]], write_ports: Set[Tuple[str, str, str]], name: Optional[str] = None, ): """ Infer a VHDL synchronous reads and writes. Parameters ---------- f : :class:`io.TextIOWrapper` The TextIOWrapper to write the VHDL code onto. clk : str Name of clock identifier to the synchronous memory. read_ports : Set[Tuple[str,str]] A set of strings used as identifiers for the read ports of the memory. write_ports : Set[Tuple[str,str,str]] A set of strings used as identifiers for the write ports of the memory. name : Optional[str] An optional name for the memory process. """ assert len(read_ports) >= 1 assert len(write_ports) >= 1 write_synchronous_process_prologue(f, clk=clk, name=name, indent=len(VHDL_TAB)) for read_name, address, re in read_ports: f.write(f'{3*VHDL_TAB}if {re} = \'1\' then\n') f.write(f'{4*VHDL_TAB}{read_name} <= memory({address});\n') f.write(f'{3*VHDL_TAB}end if;\n') for write_name, address, we in write_ports: f.write(f'{3*VHDL_TAB}if {we} = \'1\' then\n') f.write(f'{4*VHDL_TAB}memory({address}) <= {write_name};\n') f.write(f'{3*VHDL_TAB}end if;\n') write_synchronous_process_epilogue(f, clk=clk, name=name, indent=len(VHDL_TAB)) def write_asynchronous_read_memory( f: TextIOWrapper, clk: str, read_ports: Set[Tuple[str, str, str]], write_ports: Set[Tuple[str, str, str]], name: Optional[str] = None, ): """ Infer a VHDL synchronous reads and writes. Parameters ---------- f : :class:`io.TextIOWrapper` The TextIOWrapper to write the VHDL code onto. clk : str Name of clock identifier to the synchronous memory. read_ports : Set[Tuple[str,str]] A set of strings used as identifiers for the read ports of the memory. write_ports : Set[Tuple[str,str,str]] A set of strings used as identifiers for the write ports of the memory. name : Optional[str] An optional name for the memory process. """ assert len(read_ports) >= 1 assert len(write_ports) >= 1 write_synchronous_process_prologue(f, clk=clk, name=name, indent=len(VHDL_TAB)) for write_name, address, we in write_ports: f.write(f'{3*VHDL_TAB}if {we} = \'1\' then\n') f.write(f'{4*VHDL_TAB}memory({address}) <= {write_name};\n') f.write(f'{3*VHDL_TAB}end if;\n') write_synchronous_process_epilogue(f, clk=clk, name=name, indent=len(VHDL_TAB)) for read_name, address, _ in read_ports: f.write(f'{1*VHDL_TAB}{read_name} <= memory({address});\n')