Newer
Older
"""
Generation of common VHDL constructs
"""
import re
from datetime import datetime
from subprocess import PIPE, Popen
from typing import Any, Optional, Set, TextIO, Tuple
from b_asic.codegen.vhdl import write, write_lines
def b_asic_preamble(f: TextIO):
Write a standard BASIC VHDL preamble comment.
Parameters
----------
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()
f,
[
(0, '--'),
(0, '-- This code was automatically generated by the B-ASIC toolbox.'),
(0, f'-- Code generation timestamp: ({datetime.now()})'),
],
)
write(f, 0, f'-- B-ASIC short commit hash: {git_commit_id}')
write_lines(
f,
[
(0, '-- URL: https://gitlab.liu.se/da/B-ASIC'),
(0, '--', '\n\n'),
],
)
std_logic_1164: bool = True,
numeric_std: bool = True,
):
"""
Write the standard IEEE VHDL use header.
This includes std_logic_1164 and numeric_std.
Parameters
----------
f : TextIO
The TextIO 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.
"""
write(f, 0, 'use ieee.std_logic_1164.all;')
write(f, 0, 'use ieee.numeric_std.all;')
write(f, 0, '')
def signal_declaration(
f: TextIO,
default_value: Optional[str] = None,
name_pad: Optional[int] = None,

Mikael Henriksson
committed
vivado_ram_style: Optional[str] = None,
quartus_ram_style: Optional[str] = None,
Create a VHDL signal declaration.
The declaration looks like::
signal {name} : {type} [:= {default_value}];
Parameters
----------
f : TextIO
The TextIO object to write the IEEE header to.
name : str
Signal name.
An optional default value to the signal.
name_pad : int, optional
An optional left padding value applied to the name.
vivado_ram_style : str, optional
An optional Xilinx Vivado RAM style attribute to apply to this signal
declaration. If set, exactly one of: "block", "distributed", "registers",
"ultra", "mixed" or "auto".
quartus_ram_style : str, optional
An optional Quartus Prime RAM style attribute to apply to this signal
declaration. 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
write(f, 1, f'signal {name:<{name_pad}} : {signal_type}', end='')
if default_value is not None:
write(f, 0, f' := {default_value}', end='')
write(f, 0, ';')
if vivado_ram_style is not None:
f,
[
(1, f'attribute ram_style of {name} : signal is "{vivado_ram_style}";'),
],

Mikael Henriksson
committed
)
if quartus_ram_style is not None:
f,
[
(1, f'attribute ramstyle of {name} : signal is "{quartus_ram_style}";'),
],

Mikael Henriksson
committed
)

Mikael Henriksson
committed
def alias_declaration(
f: TextIO,
name: str,
signal_type: str,
value: Optional[str] = None,
name_pad: Optional[int] = None,
):
name_pad = name_pad or 0
write(f, 1, f'alias {name:<{name_pad}} : {signal_type} is {value};')
value: Any,
name_pad: Optional[int] = None,
):
"""
Write a VHDL constant declaration with a name, a type and a value.
Parameters
----------
f : TextIO
The TextIO object to write the constant declaration to.
name : str
Signal name.
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
write(f, 1, f'constant {name:<{name_pad}} : {signal_type} := {str(value)};')
name: str,
alias: str,
):
"""
Write a VHDL type declaration with a name tied to an alias.
Parameters
----------
f : TextIO
The TextIO object to write the type declaration to.
name : str
Type name alias.
alias : str
The type to tie the new name to.
"""
write(f, 1, f'type {name} is {alias};')

Mikael Henriksson
committed
sensitivity_list: str,
indent: int = 1,
name: Optional[str] = None,
):
"""
Write the prologue of a regular VHDL process with a user provided sensitivity list.
This method should almost always be followed by a :func:`process_epilogue`.
Parameters
----------
f : TextIO
The TextIO object to write the type declaration to.

Mikael Henriksson
committed
sensitivity_list : str
Content of the process sensitivity list.
indent : int, default: 1
Indentation level to use for this process.

Mikael Henriksson
committed
An optional name for the process.

Mikael Henriksson
committed
if name is not None:
write(f, indent, f'{name}: process({sensitivity_list})')

Mikael Henriksson
committed
else:
write(f, indent, f'process({sensitivity_list})')
write(f, indent, 'begin')

Mikael Henriksson
committed

Mikael Henriksson
committed
sensitivity_list: Optional[str] = None,
indent: int = 1,

Mikael Henriksson
committed
name: Optional[str] = None,
):
"""
Write the epilogue of a regular VHDL process.

Mikael Henriksson
committed
Parameters
----------
f : TextIO
The TextIO object to write the type declaration to.
Content of the process sensitivity list. Not needed when writing the epilogue.
indent : int, default: 1
Indentation level to use for this process.
indent : int, default: 1
Indentation level to use for this process.

Mikael Henriksson
committed
An optional name of the ending process.
"""
_ = sensitivity_list
write(f, indent, 'end process', end="")

Mikael Henriksson
committed
if name is not None:
write(f, 0, ' ' + name, end="")
write(f, 0, ';')
indent: int = 1,
name: Optional[str] = None,
):
"""
Write the prologue of a regular VHDL synchronous process with a single clock object.
The clock is the only item in the sensitivity list and is triggering a rising edge
block by some body of VHDL code.
This method is almost always followed by a :func:`synchronous_process_epilogue`.
Parameters
----------
f : TextIO
The TextIO to write the VHDL code onto.
clk : str
Name of the clock.
indent : int, default: 1
Indentation level to use for this process.

Mikael Henriksson
committed
An optional name for the process.
process_prologue(f, sensitivity_list=clk, indent=indent, name=name)
write(f, indent + 1, 'if rising_edge(clk) then')
indent: int = 1,
name: Optional[str] = None,
):
"""
Write the epilogue of a regular VHDL synchronous process with a single clock.
The clock is the only item in the sensitivity list and is triggering a rising edge
block by some body of VHDL code.
Parameters
----------
f : TextIO
The TextIO to write the VHDL code onto.
Name of the clock.
indent : int, default: 1
Indentation level to use for this process.
name : str, optional
An optional name for the process.
write(f, indent + 1, 'end if;')
process_epilogue(f, sensitivity_list=clk, indent=indent, name=name)

Mikael Henriksson
committed

Mikael Henriksson
committed
clk: str,
body: str,
indent: int = 1,

Mikael Henriksson
committed
name: Optional[str] = None,
):
"""
Write a regular VHDL synchronous process with a single clock.
The clock is the only item in the sensitivity list and is triggering a rising edge
block by some body of VHDL code.

Mikael Henriksson
committed
Parameters
----------
f : TextIO
The TextIO to write the VHDL code onto.

Mikael Henriksson
committed
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 : str, optional
An optional name for the process.

Mikael Henriksson
committed
"""
synchronous_process_prologue(f, clk, indent, name)

Mikael Henriksson
committed
for line in body.split('\n'):
if len(line):
write(f, indent + 2, f'{line}')
synchronous_process_epilogue(f, clk, indent, name)
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 : TextIO
The TextIO 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
synchronous_process_prologue(f, clk=clk, name=name)
for read_name, address, read_enable in read_ports:
f,
[
(3, f'if {read_enable} = \'1\' then'),
(4, f'{read_name} <= memory({address});'),
],
)
for write_name, address, we in write_ports:
f,
[
(3, f'if {we} = \'1\' then'),
(4, f'memory({address}) <= {write_name};'),
],
)
synchronous_process_epilogue(f, clk=clk, name=name)
clk: str,
read_ports: Set[Tuple[str, str, str]],
write_ports: Set[Tuple[str, str, str]],
name: Optional[str] = None,
):
"""

Mikael Henriksson
committed
Infer a VHDL memory with synchronous writes and asynchronous reads.
Parameters
----------
f : TextIO
The TextIO to write the VHDL code onto.
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
synchronous_process_prologue(f, clk=clk, name=name)
for write_name, address, we in write_ports:
f,
[
(3, f'if {we} = \'1\' then'),
(4, f'memory({address}) <= {write_name};'),
],
)
synchronous_process_epilogue(f, clk=clk, name=name)
for read_name, address, _ in read_ports:
write(f, 1, f'{read_name} <= memory({address});')
def is_valid_vhdl_identifier(identifier: str) -> bool:
"""
Test if identifier is a valid VHDL identifier, as specified by VHDL 2019.
An identifier is a valid VHDL identifier if it is not a VHDL reserved keyword and
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
it is a valid basic identifier as specified by IEEE STD 1076-2019 (VHDL standard).
Parameters
----------
identifier : str
The identifier to test.
Returns
-------
Returns True if identifier is a valid VHDL identifier, False otherwise.
"""
# IEEE STD 1076-2019:
# Sec. 15.4.2, Basic identifiers:
# * A basic identifier consists only of letters, digits, and underlines.
# * A basic identifier is not a reserved VHDL keyword
is_basic_identifier = (
re.fullmatch(pattern=r'[a-zA-Z][0-9a-zA-Z_]*', string=identifier) is not None
)
return is_basic_identifier and not is_vhdl_reserved_keyword(identifier)
def is_vhdl_reserved_keyword(identifier: str) -> bool:
"""
Test if identifier is a reserved VHDL keyword.
Parameters
----------
identifier : str
The identifier to test.
Returns
-------
Returns True if identifier is reserved, False otherwise.
"""
# List of reserved keyword in IEEE STD 1076-2019.
# Sec. 15.10, Reserved words:
reserved_keywords = (
"abs",
"access",
"after",
"alias",
"all",
"and",
"architecture",
"array",
"assert",
"assume",
"attribute",
"begin",
"block",
"body",
"buffer",
"bus",
"case",
"component",
"configuration",
"constant",
"context",
"cover",
"default",
"disconnect",
"downto",
"else",
"elsif",
"end",
"entity",
"exit",
"fairness",
"file",
"for",
"force",
"function",
"generate",
"generic",
"group",
"guarded",
"if",
"impure",
"in",
"inertial",
"inout",
"is",
"label",
"library",
"linkage",
"literal",
"loop",
"map",
"mod",
"nand",
"new",
"next",
"nor",
"not",
"null",
"of",
"on",
"open",
"or",
"others",
"out",
"package",
"parameter",
"port",
"postponed",
"procedure",
"process",
"property",
"protected",
"private",
"pure",
"range",
"record",
"register",
"reject",
"release",
"rem",
"report",
"restrict",
"return",
"rol",
"ror",
"select",
"sequence",
"severity",
"signal",
"shared",
"sla",
"sll",
"sra",
"srl",
"strong",
"subtype",
"then",
"to",
"transport",
"type",
"unaffected",
"units",
"until",
"use",
"variable",
"view",
"vpkg",
"vmode",
"vprop",
"vunit",
"wait",
"when",
"while",
"with",
"xnor",
"xor",
)
return identifier.lower() in reserved_keywords