# Manuel Eggimann <meggimann@iis.ee.ethz.ch>
#
# Copyright (C) 2020-2022 ETH Zürich
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import textwrap
from pathlib import Path
from typing import Mapping, List, TextIO
from dumpling.Common.VectorBuilder import VectorBuilder
from mako.template import Template
[docs]class HP93000VectorReader:
"""
The HP93000VectorReader allows parsing of AVC files back to the intermediate representation of vectors in the
form of dictionaries.
In order to not exhaust the whole system memory when parsing huge AVC files, the Class provides the
`self.vectors()` generator function that iteratively reads and parses the underlying AVC file while iterating.
Additionally the `HP93000VectorReader` implements the the context manager interface to automatically close the
underlying file once all vectors have been consumed.
Examples::
with HP93000VectorReader('my_vectors.avc', my_pin_declarations) as reader:
for vector in reader.vectors():
..do something usefull with the vector...
Args:
stimuli_file_path(Path): The path to the AVC file to parse
pins(Mapping[str, Mapping]): The pin declaration that allows the parser to perform the inverse logical to
physical pin name mapping (i.e. the same pin declaration that was used to generate the vectors with
`VectorBuilder`
"""
_matchers = {
'empty_line' : re.compile(r'^\s*$'),
'format_stmt' : re.compile(r'FORMAT\s+(?P<ports>(\w+(?:\s+|;))+)'),
'port_stmt' : re.compile(r'PORT\s+\w*\s*;'),
'normal_vec' : re.compile(r'R(?P<repeat>\d+)\s+(?P<dvc_name>\w*)\s+(?P<pin_state>\w+)\s+(?:\[%]\s*(?P<comment>.*);)?'),
'match_loop_begin' : re.compile(r'SQPG\s+MACT\s+(?P<retries>\d+)\s*;'),
'match_loop_idle_begin' : re.compile(r'SQPG\s+MRPT\s+(?P<idle_vectors>\d+)\s*;'),
'match_loop_end' : re.compile(r'SQPG\s+PADDING\s*;'),
'loop_begin' : re.compile(r'SQPG\s+LBGN\s+(?P<count>\d+)\s*;'),
'loop_end' : re.compile(r'SQPG\s+LEND\s*;')
}
def __init__(self, stimuli_file_path: Path, pins: Mapping[str, Mapping]):
self.stimuli_file_path = stimuli_file_path
self.pins = pins
self.pin_order = None
self.physical_to_logical_map = {pin.get('avc_name', pin['name']): logical_name for logical_name, pin in pins.items()}
def __enter__(self):
self._file = self.stimuli_file_path.open('r')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._file.close()
[docs] def vectors(self):
"""
A generator function that yields a single parsed vector at a time.
Yields:
Mapping: A single vector
Raises:
StopIteration: Once all vectors have been consumed
"""
for line in self._file:
stm_type = None
match = None
for stm_type, matcher in HP93000VectorReader._matchers.items():
match = matcher.match(line)
if match:
break
if not match:
raise ValueError("Could not parse line: {}".format(line))
if stm_type == 'empty_line':
pass
elif stm_type == 'format_stmt':
self.pin_order = str.split(match.group('ports'))
elif stm_type == 'port_stmt':
pass #Ignore this statement
elif stm_type == 'normal_vec':
if self.pin_order: #Make sure we already read the format statement
pin_state = {self.physical_to_logical_map[self.pin_order[i]]: value for i, value in enumerate(match.group('pin_state'))}
yield {'type': 'vec', 'vector': pin_state, 'repeat': int(match.group('repeat')), 'comment': match.group('comment')}
else:
raise ValueError("Encountered vector statement before reading format statement")
elif stm_type == 'match_loop_begin':
condition_vectors = list(self.vectors())
idle_vectors = list(self.vectors())
yield {'type': 'match_loop', 'cond_vectors': condition_vectors, 'idle_vectors': idle_vectors, 'retries': int(match.group('retries'))}
elif stm_type == 'match_loop_idle_begin':
return
elif stm_type == 'match_loop_end':
return
elif stm_type == 'loop_begin':
body = list(self.vectors())
yield {'type': 'loop', 'loop_body': body, 'repeat': int(match.group('count'))}
elif stm_type == 'loop_end':
return
[docs]class HP93000VectorWriter:
"""
This class allows to generate AVC files from Vectors in intermediate (dictionary) representation as generated by
the `VectorBuilder` class.
During initialization, the object is initialized with a pins declaration dictionary (see documentation of the
`VectorBuilder` class) and output AVC file path. Optionally, a port, device_cycle_name and wavetable name can be
supplied. In addition to creating the avc output file, a wave table file (*.wtb) and a timing format file (*.tmf) is
generated upon construction of the `HP93000VectorWriter` instance. These files will have the same base name ( e.g.
vectors.tmf and vectors.wtb if `stimuli_file_path`=='output.avc'. This allows to directly import the generated
vectors for a specific test port given that the `port` argument was chosen according to the port name used in the
tester setup.
The class implements the context manager interface that automatically closes the underlying avc file. This allows to
write vectors to AVC file in batches::
with HP93000VectorWriter('output_vectors.avc', dut_pins) as writer:
vectors = ...generate some vectors...
writer.write_vectors(vectors)
vectors = ...generate some more vectors...
writer.write_vectors(vectors) #Append them to the AVC file
Args:
stimuli_file_path(Path): The path of the output AVC file
pins(Mapping[str, Mapping]): The pin declaration dictionary (see `VectorBuilder` docstring)
port(str): If not None, PORT declaration is added to the header of the AVC file to make it importable as a
port specific vector file.
wtb_name: The name of the wavetable to be associated with the AVC pattern file.
"""
def __init__(self, stimuli_file_path: Path, pins: Mapping[str, Mapping], port: str=None, device_cycle_name: str="dvc_1", wtb_name: str="Standard ATI"):
self.pins = pins
self.stimuli_file_path = stimuli_file_path
self.stimuli_file = None
self.port = port
self.device_cycle_name = device_cycle_name
self.wtb_name = wtb_name
self._generate_wtb_and_tmf()
self._write_header()
def __enter__(self):
self.stimuli_file = self.stimuli_file_path.open('a+')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.stimuli_file.close()
def _generate_wtb_and_tmf(self):
#Generate the WTB and TMF file in the same target directory as the stimuli file with the same stem
wtb_path = self.stimuli_file_path.with_suffix('.wtb')
tmf_path = self.stimuli_file_path.with_suffix('.tmf')
#Write the default wtb
wtb_path.write_text(self.wtb_name)
#Write the default tmf
wtb_template = Template(textwrap.dedent("""\
PINS ${port_name}
DDC ${device_cycle_name}
0 0
1 1
X 2
L 3
H 4
Z 5"""))
tmf_path.write_text(wtb_template.render(port_name=self.port, device_cycle_name=self.device_cycle_name))
[docs] def write_vectors(self, vectors: List[Mapping[str, None]], compress=False):
"""
Append the given vectors to the vector file in AVC format.
This method translates the intermediate vector representation generated by `VectorBuilder` or
`HP93000VectorReader` to the AVC format that can be imported into the ASIC tester.
The function allows to optionally apply run length compression on the vectors by merging subsequent identical
vectors to a single entry with increased 'repeat' attribute. This allows to safe vector memory but might make
the vectors harder to interpret during debugging.
Args:
vectors(List): A list of vectors to translate and append to the AVC file.
compress(bool): If true, apply compression to the vector before writing them to the AVC file.
Returns:
"""
if compress:
vectors = VectorBuilder.compress_vectors(vectors)
for vector in vectors:
if vector['type'] == 'vec':
pin_state_string = ''.join([str(vector['vector'][pin]) for pin in sorted(self.pins.keys())])
vector_line = "R{} {} {} ".format(vector['repeat'], self.device_cycle_name, pin_state_string)
if vector['comment'] and vector['comment'] != "":
vector_line += "[%] " + vector['comment'] + " "
vector_line += ";\n"
self.stimuli_file.write(vector_line)
elif vector['type'] == 'match_loop':
self.stimuli_file.write("SQPG MACT {} ;\n".format(vector['retries']))
self.write_vectors(vector['cond_vectors'])
self.stimuli_file.write("SQPG MRPT {} ;\n".format(len(vector['idle_vectors'])))
self.write_vectors(vector['idle_vectors'])
self.stimuli_file.write("SQPG PADDING ;\n")
elif vector['type'] == 'loop':
self.stimuli_file.write("SQPG LBGN {} ;\n".format(vector['repeat']))
self.write_vectors(vector['loop_body'])
self.stimuli_file.write("SQPG LEND ;\n")
else:
raise ValueError("Got vector with unknown type {}".format(vector['type']))
def _write_header(self):
with self.stimuli_file_path.open(mode='w') as stimuli_file:
if self.port:
stimuli_file.write("PORT " + self.port + " ;\n")
stimuli_file.write("FORMAT " + ' '.join([pin['name'] for logical_name, pin in sorted(self.pins.items())]) + " ;\n")