Skip to content
Snippets Groups Projects
common.py 9.37 KiB
Newer Older
  • Learn to ignore specific revisions
  • """
    Generation of common VHDL constructs
    """
    
    from datetime import datetime
    from io import TextIOWrapper
    
    from subprocess import PIPE, Popen
    
    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.
    
        f : :class:`io.TextIOWrapper`
            The file object to write the header to.
    
        # Try to acquire the current git commit hash
        git_commit_id = None
        try:
            process = Popen(['git', 'rev-parse', '--short', 'HEAD'], stdout=PIPE)
            git_commit_id = process.communicate()[0].decode('utf-8').strip()
        except:
            pass
    
        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')
    
        if git_commit_id:
            f.write(f'-- B-ASIC short commit hash: {git_commit_id}\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(
    
        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.
    
        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.
    
            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')