Skip to content
Snippets Groups Projects
Commit d26efc94 authored by Mikael Henriksson's avatar Mikael Henriksson :runner:
Browse files

add support for testing valid VHDL identifiers (closes #246)

parent bae4acaf
No related branches found
No related tags found
No related merge requests found
Pipeline #96949 passed
...@@ -8,6 +8,7 @@ from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple, Union, ...@@ -8,6 +8,7 @@ from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple, Union,
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from graphviz import Digraph from graphviz import Digraph
from b_asic.codegen.vhdl.common import is_valid_vhdl_identifier
from b_asic.port import InputPort, OutputPort from b_asic.port import InputPort, OutputPort
from b_asic.process import MemoryVariable, OperatorProcess, PlainMemoryVariable from b_asic.process import MemoryVariable, OperatorProcess, PlainMemoryVariable
from b_asic.resources import ProcessCollection from b_asic.resources import ProcessCollection
...@@ -42,10 +43,8 @@ class HardwareBlock: ...@@ -42,10 +43,8 @@ class HardwareBlock:
entity_name : str entity_name : str
The entity name. The entity name.
""" """
# Should be a better check. if not is_valid_vhdl_identifier(entity_name):
# See https://stackoverflow.com/questions/7959587/regex-for-vhdl-identifier raise ValueError(f'{entity_name} is not a valid VHDL indentifier')
if " " in entity_name:
raise ValueError("Cannot have space in entity name")
self._entity_name = entity_name self._entity_name = entity_name
def write_code(self, path: str) -> None: def write_code(self, path: str) -> None:
...@@ -57,7 +56,7 @@ class HardwareBlock: ...@@ -57,7 +56,7 @@ class HardwareBlock:
path : str path : str
Directory to write code in. Directory to write code in.
""" """
if not self._entity_name: if not self.entity_name:
raise ValueError("Entity name must be set") raise ValueError("Entity name must be set")
raise NotImplementedError raise NotImplementedError
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
Generation of common VHDL constructs Generation of common VHDL constructs
""" """
import re
from datetime import datetime from datetime import datetime
from io import TextIOWrapper from io import TextIOWrapper
from subprocess import PIPE, Popen from subprocess import PIPE, Popen
...@@ -350,11 +351,11 @@ def synchronous_memory( ...@@ -350,11 +351,11 @@ def synchronous_memory(
assert len(read_ports) >= 1 assert len(read_ports) >= 1
assert len(write_ports) >= 1 assert len(write_ports) >= 1
synchronous_process_prologue(f, clk=clk, name=name) synchronous_process_prologue(f, clk=clk, name=name)
for read_name, address, re in read_ports: for read_name, address, read_enable in read_ports:
vhdl.write_lines( vhdl.write_lines(
f, f,
[ [
(3, f'if {re} = \'1\' then'), (3, f'if {read_enable} = \'1\' then'),
(4, f'{read_name} <= memory({address});'), (4, f'{read_name} <= memory({address});'),
(3, 'end if;'), (3, 'end if;'),
], ],
...@@ -409,3 +410,165 @@ def asynchronous_read_memory( ...@@ -409,3 +410,165 @@ def asynchronous_read_memory(
synchronous_process_epilogue(f, clk=clk, name=name) synchronous_process_epilogue(f, clk=clk, name=name)
for read_name, address, _ in read_ports: for read_name, address, _ in read_ports:
vhdl.write(f, 1, f'{read_name} <= memory({address});') vhdl.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 indentifier is a valid VHDL identifier if it is not a VHDL reserved keyword and
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'[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
...@@ -10,6 +10,7 @@ from matplotlib.axes import Axes ...@@ -10,6 +10,7 @@ from matplotlib.axes import Axes
from matplotlib.ticker import MaxNLocator from matplotlib.ticker import MaxNLocator
from b_asic._preferences import LATENCY_COLOR, WARNING_COLOR from b_asic._preferences import LATENCY_COLOR, WARNING_COLOR
from b_asic.codegen.vhdl.common import is_valid_vhdl_identifier
from b_asic.process import MemoryVariable, OperatorProcess, PlainMemoryVariable, Process from b_asic.process import MemoryVariable, OperatorProcess, PlainMemoryVariable, Process
from b_asic.types import TypeName from b_asic.types import TypeName
...@@ -818,7 +819,7 @@ class ProcessCollection: ...@@ -818,7 +819,7 @@ class ProcessCollection:
self, self,
heuristic: str = "graph_color", heuristic: str = "graph_color",
coloring_strategy: str = "saturation_largest_first", coloring_strategy: str = "saturation_largest_first",
) -> Set["ProcessCollection"]: ) -> List["ProcessCollection"]:
""" """
Split a ProcessCollection based on overlapping execution time. Split a ProcessCollection based on overlapping execution time.
...@@ -843,7 +844,7 @@ class ProcessCollection: ...@@ -843,7 +844,7 @@ class ProcessCollection:
Returns Returns
------- -------
A set of new ProcessCollection objects with the process splitting. A list of new ProcessCollection objects with the process splitting.
""" """
if heuristic == "graph_color": if heuristic == "graph_color":
exclusion_graph = self.create_exclusion_graph_from_execution_time() exclusion_graph = self.create_exclusion_graph_from_execution_time()
...@@ -862,7 +863,7 @@ class ProcessCollection: ...@@ -862,7 +863,7 @@ class ProcessCollection:
read_ports: Optional[int] = None, read_ports: Optional[int] = None,
write_ports: Optional[int] = None, write_ports: Optional[int] = None,
total_ports: Optional[int] = None, total_ports: Optional[int] = None,
) -> Set["ProcessCollection"]: ) -> List["ProcessCollection"]:
""" """
Split this process storage based on concurrent read/write times according. Split this process storage based on concurrent read/write times according.
...@@ -907,7 +908,7 @@ class ProcessCollection: ...@@ -907,7 +908,7 @@ class ProcessCollection:
write_ports: int, write_ports: int,
total_ports: int, total_ports: int,
coloring_strategy: str = "saturation_largest_first", coloring_strategy: str = "saturation_largest_first",
) -> Set["ProcessCollection"]: ) -> List["ProcessCollection"]:
""" """
Parameters Parameters
---------- ----------
...@@ -944,7 +945,7 @@ class ProcessCollection: ...@@ -944,7 +945,7 @@ class ProcessCollection:
def _split_from_graph_coloring( def _split_from_graph_coloring(
self, self,
coloring: Dict[Process, int], coloring: Dict[Process, int],
) -> Set["ProcessCollection"]: ) -> List["ProcessCollection"]:
""" """
Split :class:`Process` objects into a set of :class:`ProcessesCollection` Split :class:`Process` objects into a set of :class:`ProcessesCollection`
objects based on a provided graph coloring. objects based on a provided graph coloring.
...@@ -964,10 +965,10 @@ class ProcessCollection: ...@@ -964,10 +965,10 @@ class ProcessCollection:
process_collection_set_list = [set() for _ in range(max(coloring.values()) + 1)] process_collection_set_list = [set() for _ in range(max(coloring.values()) + 1)]
for process, color in coloring.items(): for process, color in coloring.items():
process_collection_set_list[color].add(process) process_collection_set_list[color].add(process)
return { return [
ProcessCollection(process_collection_set, self._schedule_time, self._cyclic) ProcessCollection(process_collection_set, self._schedule_time, self._cyclic)
for process_collection_set in process_collection_set_list for process_collection_set in process_collection_set_list
} ]
def _repr_svg_(self) -> str: def _repr_svg_(self) -> str:
""" """
...@@ -1076,7 +1077,7 @@ class ProcessCollection: ...@@ -1076,7 +1077,7 @@ class ProcessCollection:
filename: str, filename: str,
entity_name: str, entity_name: str,
word_length: int, word_length: int,
assignment: Set['ProcessCollection'], assignment: List['ProcessCollection'],
read_ports: int = 1, read_ports: int = 1,
write_ports: int = 1, write_ports: int = 1,
total_ports: int = 2, total_ports: int = 2,
...@@ -1116,6 +1117,10 @@ class ProcessCollection: ...@@ -1116,6 +1117,10 @@ class ProcessCollection:
(which is added automatically). For large interleavers, this can improve (which is added automatically). For large interleavers, this can improve
timing significantly. timing significantly.
""" """
# Check that entity name is a valid VHDL identifier
if not is_valid_vhdl_identifier(entity_name):
raise KeyError(f'{entity_name} is not a valid identifier')
# Check that this is a ProcessCollection of (Plain)MemoryVariables # Check that this is a ProcessCollection of (Plain)MemoryVariables
is_memory_variable = all( is_memory_variable = all(
isinstance(process, MemoryVariable) for process in self._collection isinstance(process, MemoryVariable) for process in self._collection
...@@ -1249,6 +1254,10 @@ class ProcessCollection: ...@@ -1249,6 +1254,10 @@ class ProcessCollection:
The total number of ports used when splitting process collection based on The total number of ports used when splitting process collection based on
memory variable access. memory variable access.
""" """
# Check that entity name is a valid VHDL identifier
if not is_valid_vhdl_identifier(entity_name):
raise KeyError(f'{entity_name} is not a valid identifier')
# Check that this is a ProcessCollection of (Plain)MemoryVariables # Check that this is a ProcessCollection of (Plain)MemoryVariables
is_memory_variable = all( is_memory_variable = all(
isinstance(process, MemoryVariable) for process in self._collection isinstance(process, MemoryVariable) for process in self._collection
......
...@@ -64,8 +64,8 @@ outputs.show(title="Output executions") ...@@ -64,8 +64,8 @@ outputs.show(title="Output executions")
p1 = ProcessingElement(adders, entity_name="adder") p1 = ProcessingElement(adders, entity_name="adder")
p2 = ProcessingElement(mults, entity_name="cmul") p2 = ProcessingElement(mults, entity_name="cmul")
p_in = ProcessingElement(inputs, entity_name='in') p_in = ProcessingElement(inputs, entity_name='input')
p_out = ProcessingElement(outputs, entity_name='out') p_out = ProcessingElement(outputs, entity_name='output')
# %% # %%
# Extract memory variables # Extract memory variables
......
from b_asic.codegen.vhdl.common import is_valid_vhdl_identifier
def test_is_valid_vhdl_identifier():
identifier_pass = {
"COUNT",
"X",
"c_out",
"FFT",
"Decoder",
"VHSIC",
"X1",
"PageCount",
"STORE_NEXT_ITEM",
"ValidIdentifier123",
"123valid123",
"valid_identifier",
}
identifier_fail = {
"architecture",
"Architecture",
"ArChItEctUrE",
"architectURE",
"entity",
"invalid+",
"invalid}",
"not-valid",
"(invalid)",
"invalid£",
}
for identifier in identifier_pass:
assert is_valid_vhdl_identifier(identifier)
for identifier in identifier_fail:
assert not is_valid_vhdl_identifier(identifier)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment