-
Oscar Gustafsson authoredOscar Gustafsson authored
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});')