Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
common.py 12.02 KiB
"""
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 import vhdl


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.
    """
    # 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
    vhdl.write_lines(
        f,
        [
            (0, '--'),
            (0, '-- This code was automatically generated by the B-ASIC toolbox.'),
            (0, f'-- Code generation timestamp: ({datetime.now()})'),
        ],
    )
    if git_commit_id:
        vhdl.write(f, 0, f'-- B-ASIC short commit hash: {git_commit_id}')
    vhdl.write_lines(
        f,
        [
            (0, '-- URL: https://gitlab.liu.se/da/B-ASIC'),
            (0, '--', '\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.
    """
    vhdl.write(f, 0, 'library ieee;')
    if std_logic_1164:
        vhdl.write(f, 0, 'use ieee.std_logic_1164.all;')
    if numeric_std:
        vhdl.write(f, 0, 'use ieee.numeric_std.all;')
    vhdl.write(f, 0, '')


def write_signal_decl(
    f: TextIOWrapper,
    name: str,
    type: str,
    default_value: Optional[str] = None,
    name_pad: Optional[int] = None,
    vivado_ram_style: Optional[str] = None,
    quartus_ram_style: Optional[str] = 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 : string, optional
        An optional default value to the signal.
    name_pad : int, optional
        An optional left padding value applied to the name.
    vivado_ram_style : string, optional
        An optional Xilinx Vivado RAM style attribute to apply to this signal delcaration.
        If set, exactly one of: "block", "distributed", "registers", "ultra", "mixed" or "auto".
    quartus_ram_style : string, optional
        An optional Quartus Prime RAM style attribute to apply to this signal delcaration.
        If set, exactly one of: "M4K", "M9K", "M10K", "M20K", "M144K", "MLAB" or "logic".
    """
    # Spacing of VHDL signals declaration always with a single tab
    name_pad = name_pad or 0
    vhdl.write(f, 1, f'signal {name:<{name_pad}} : {type}', end='')
    if default_value is not None:
        vhdl.write(f, 0, f' := {default_value}', end='')
    vhdl.write(f, 0, ';')
    if vivado_ram_style is not None:
        vhdl.write_lines(
            f,
            [
                (1, 'attribute ram_style : string;'),
                (1, f'attribute ram_style of {name} : signal is "{vivado_ram_style}";'),
            ],
        )
    if quartus_ram_style is not None:
        vhdl.write_lines(
            f,
            [
                (1, 'attribute ramstyle : string;'),
                (1, f'attribute ramstyle of {name} : signal is "{quartus_ram_style}";'),
            ],
        )


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
    vhdl.write(f, 1, f'constant {name:<{name_pad}} : {type} := {str(value)};')


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.
    """
    vhdl.write(f, 1, f'type {name} is {alias};')


def write_process_prologue(
    f: TextIOWrapper,
    sensitivity_list: str,
    indent: int = 1,
    name: Optional[str] = None,
):
    """
    Write only the prologue of a regular VHDL process with a user provided sensitivity list.
    This method should almost always guarantely be followed by a write_process_epilogue.

    Parameters
    ----------
    f : :class:`io.TextIOWrapper`
        The TextIOWrapper object to write the type declaration to.
    sensitivity_list : str
        Content of the process sensitivity list.
    indent : int, default: 1
        Indentation level to use for this process.
    name : Optional[str]
        An optional name for the process.
    """
    if name is not None:
        vhdl.write(f, indent, f'{name}: process({sensitivity_list})')
    else:
        vhdl.write(f, indent, f'process({sensitivity_list})')
    vhdl.write(f, indent, 'begin')


def write_process_epilogue(
    f: TextIOWrapper,
    sensitivity_list: Optional[str] = None,
    indent: int = 1,
    name: Optional[str] = None,
):
    """
    Parameters
    ----------
    f : :class:`io.TextIOWrapper`
        The TextIOWrapper object to write the type declaration to.
    sensitivity_list : str
        Content of the process sensitivity list. Not needed when writing the epligoue.
    indent : int, default: 1
        Indentation level to use for this process.
    indent : int, default: 1
        Indentation level to use for this process.
    name : Optional[str]
        An optional name of the ending process.
    """
    _ = sensitivity_list
    vhdl.write(f, indent, 'end process', end="")
    if name is not None:
        vhdl.write(f, 0, ' ' + name, end="")
    vhdl.write(f, 0, ';')


def write_synchronous_process_prologue(
    f: TextIOWrapper,
    clk: str,
    indent: int = 1,
    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 : int, default: 1
        Indentation level to use for this process.
    name : Optional[str]
        An optional name for the process.
    """
    write_process_prologue(f, sensitivity_list=clk, indent=indent, name=name)
    vhdl.write(f, indent + 1, 'if rising_edge(clk) then')


def write_synchronous_process_epilogue(
    f: TextIOWrapper,
    clk: Optional[str],
    indent: int = 1,
    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 : int, default: 1
        Indentation level to use for this process.
    name : Optional[str]
        An optional name for the process
    """
    _ = clk
    vhdl.write(f, indent + 1, 'end if;')
    write_process_epilogue(f, sensitivity_list=clk, indent=indent, name=name)


def write_synchronous_process(
    f: TextIOWrapper,
    clk: str,
    body: str,
    indent: int = 1,
    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 : int, default: 1
        Indentation level to use for this process.
    name : Optional[str]
        An optional name for the process
    """
    write_synchronous_process_prologue(f, clk, indent, name)
    for line in body.split('\n'):
        if len(line):
            vhdl.write(f, indent + 2, f'{line}')
    write_synchronous_process_epilogue(f, clk, indent, name)


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)
    for read_name, address, re in read_ports:
        vhdl.write_lines(
            f,
            [
                (3, f'if {re} = \'1\' then'),
                (4, f'{read_name} <= memory({address});'),
                (3, 'end if;'),
            ],
        )
    for write_name, address, we in write_ports:
        vhdl.write_lines(
            f,
            [
                (3, f'if {we} = \'1\' then'),
                (4, f'memory({address}) <= {write_name};'),
                (3, 'end if;'),
            ],
        )
    write_synchronous_process_epilogue(f, clk=clk, name=name)


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 memory with synchronous writes and asynchronous reads.

    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)
    for write_name, address, we in write_ports:
        vhdl.write_lines(
            f,
            [
                (3, f'if {we} = \'1\' then'),
                (4, f'memory({address}) <= {write_name};'),
                (3, 'end if;'),
            ],
        )
    write_synchronous_process_epilogue(f, clk=clk, name=name)
    for read_name, address, _ in read_ports:
        vhdl.write(f, 1, f'{read_name} <= memory({address});')