@@ -56,50 +56,90 @@ class Element:
5656 This is either a testpoint or a covergroup.
5757 """
5858
59- # Type of the testplan element. Must be set by the extended class.
60- kind = "none"
59+ @staticmethod
60+ def get_str (src : dict [str , Any ], field_name : str , elt_name : str | None ) -> str :
61+ """Get the named field from src, whose value should be a string.
62+
63+ If the field is missing or its value is not a string, raise a ValueError.
64+
65+ Args:
66+ src: The dictionary being read.
6167
62- # Mandatory fields in a testplan element.
63- fields = ["name" , "desc" ]
68+ field_name: The key whose value should be returned.
6469
65- def __init__ (self , raw_dict ) -> None :
70+ elt_name: The name of the Element being parsed (if known).
71+
72+ """
73+ raw = src .get (field_name )
74+ if raw is None :
75+ name_comment = f" with name { elt_name } " if elt_name is not None else ""
76+ msg = f"Testplan element{ name_comment } does not have a { field_name } field."
77+ raise ValueError (msg )
78+
79+ if not isinstance (raw , str ):
80+ name_comment = f" with name { elt_name } " if elt_name is not None else ""
81+ msg = (
82+ f"Testplan element{ name_comment } has a { field_name } field but this is not a string."
83+ )
84+ raise TypeError (msg )
85+
86+ return raw
87+
88+ def __init__ (self , raw_dict : dict [str , Any ]) -> None :
6689 """Initialize the testplan element.
6790
6891 raw_dict is the dictionary parsed from the Hjson file.
6992 """
70- # 'tags' is an optional field in addition to the mandatory self.fields.
71- self .tags = []
93+ self . name = Element . get_str ( raw_dict , "name" , None )
94+ self .desc = Element . get_str ( raw_dict , "desc" , self . name )
7295
73- for field in self .fields :
74- try :
75- setattr (self , field , raw_dict .pop (field ))
76- except KeyError as e :
77- msg = (
78- f"Error: { self .kind } does not contain all of "
79- f"the required fields:\n { raw_dict } \n Required:\n "
80- f"{ self .fields } \n { e } "
81- )
82- raise KeyError (
83- msg ,
84- )
96+ # Check that the name field is not empty
97+ if not self .name :
98+ raise ValueError ("Cannot have a testplan element with empty name." )
8599
86- # Set the remaining k-v pairs in raw_dict as instance attributes.
87- for k , v in raw_dict . items ():
88- setattr ( self , k , v )
100+ # Set a default value for self.tags as an empty list. We'll check we
101+ # haven't changed type at the end.
102+ self . tags : list [ str ] = []
89103
90- # Verify things are in order.
91- self ._validate ()
104+ # Convert all the k/v pairs in raw_dict into instance attributes for
105+ # this object. These should all either be strings or lists of strings.
106+ for k , v in raw_dict .items ():
107+ # We have extracted the element name and description already
108+ if k in ["name" , "desc" ]:
109+ continue
92110
93- def _validate (self ) -> None :
94- """Runs some basic consistency checks."""
95- if not self .name :
96- msg = f"Error: { self .kind .capitalize ()} name cannot be empty:\n { self } "
97- raise ValueError (msg )
111+ if isinstance (v , str ):
112+ setattr (self , k , v )
113+ elif isinstance (v , list ):
114+ # This is a list, but we should check slightly more: it should
115+ # be a list of strings.
116+ strings : list [str ] = []
117+ for idx , item in enumerate (v ):
118+ if isinstance (item , str ):
119+ strings .append (item )
120+ else :
121+ msg = (
122+ f"Item { idx } of the { k } field in the testplan "
123+ f"element with name { self .name } is not a string."
124+ )
125+ raise TypeError (msg )
126+ setattr (self , k , strings )
127+ else :
128+ msg = (
129+ f"The { k } field in the testplan element with name "
130+ f"{ self .name } is neither a string nor a list of strings."
131+ )
132+ raise TypeError (msg )
98133
99- # "tags", if updated key must be list.
134+ # Check that self.tags is still a list (and wasn't overwritten with a
135+ # string when parsing the raw dict)
100136 if not isinstance (self .tags , list ):
101- msg = f"'tags' key in { self } is not a list."
102- raise ValueError (msg )
137+ msg = (
138+ "The tags field in the testplan element with name "
139+ f"{ self .name } is a string but should be a list of strings "
140+ "(if supplied)."
141+ )
142+ raise TypeError (msg )
103143
104144 def has_tags (self , tags : set [str ]) -> bool :
105145 """Return true if the element matches the provided tags.
@@ -133,60 +173,89 @@ class Covergroup(Element):
133173 include individual coverpoints and crosses in the description.
134174 """
135175
136- kind = "covergroup"
176+ def __init__ (self , raw_dict : dict [str , Any ]) -> None :
177+ """Create a Covergroup based on the given parsed hjson dictionary."""
178+ super ().__init__ (raw_dict )
137179
138- def _validate (self ) -> None :
139- super ()._validate ()
140180 if not self .name .endswith ("_cg" ):
141181 msg = f'Error: Covergroup name { self .name } needs to end with suffix "_cg".'
142- raise ValueError (
143- msg ,
144- )
182+ raise ValueError (msg )
145183
146184
147185class Testpoint (Element ):
148- """An testcase entry in the testplan.
186+ """A testcase entry in the testplan.
149187
150- A testpoint maps to a unique design feature that is planned to be verified.
188+ A testpoint maps to a unique design feature that should be verified.
151189 It captures following information:
152190 - name of the planned test
153191 - a brief description indicating intent, stimulus and checking procedure
154192 - the targeted stage
155193 - the list of actual developed tests that verify it
156194 """
157195
158- kind = "testpoint"
159- fields = [* Element .fields , "stage" , "tests" ]
160-
161196 # Verification stages.
162197 stages = ("N.A." , "V1" , "V2" , "V2S" , "V3" )
163198
164- def __init__ (self , raw_dict ) -> None :
199+ def __init__ (self , raw_dict : dict [str , Any ]) -> None :
200+ """Construct a Testpoint from the given dictionary."""
201+ # These will get overridden from the dictionary, but allow the tooling
202+ # to see that the fields exist and know their expected types.
203+ self .tests : list [str ] = []
204+ self .stage : str = ""
205+
165206 super ().__init__ (raw_dict )
166207
208+ # The dictionary should have specified a list of tests (possibly empty)
209+ # for the testpoint.
210+ if "tests" not in raw_dict :
211+ msg = f"The testpoint named { self .name } has no list of tests."
212+ raise ValueError (msg )
213+ if not isinstance (self .tests , list ):
214+ msg = (
215+ f"The testpoint named { self .name } should have a list of "
216+ "tests, not a single test name."
217+ )
218+ raise TypeError (msg )
219+
220+ # The dictionary should have specified a stage for the testpoint, which
221+ # should have been a string.
222+ if "stage" not in raw_dict :
223+ msg = f"The testpoint named { self .name } has no stage."
224+ raise ValueError (msg )
225+ if not isinstance (self .stage , str ):
226+ msg = f"The stage of the testpoint named { self .name } should be a string, not a list."
227+ raise TypeError (msg )
228+ if self .stage not in Testpoint .stages :
229+ msg = (
230+ f"The stage of the testpoint named { self .name } is "
231+ f"{ self .stage } but this is not a known testpoint stage. "
232+ f"Legal values: { Testpoint .stages } ."
233+ )
234+ raise ValueError (msg )
235+
236+ # There are some special fields that we use for a Testpoint object.
237+ # Because the Element constructor allows the hjson file to set
238+ # arbitrary fields, we need to make sure that they haven't already been
239+ # set.
240+ self ._check_field_unset ("test_results" )
241+ self ._check_field_unset ("not_mapped" )
242+
167243 # List of Result objects indicating test results mapped to this
168244 # testpoint.
169- self .test_results = []
245+ self .test_results : list [ Result ] = []
170246
171247 # If tests key is set to ["N/A"], then don't map this testpoint to the
172248 # simulation results.
173- self .not_mapped = False
174- if self .tests == ["N/A" ]:
175- self .not_mapped = True
249+ self .not_mapped : bool = self .tests == ["N/A" ]
176250
177- def _validate (self ) -> None :
178- super (). _validate ()
179- if self . stage not in Testpoint . stages :
251+ def _check_field_unset (self , field_name : str ) -> None :
252+ """Check that the field with the given name has not been set in the class."""
253+ if hasattr ( self , field_name ) :
180254 msg = (
181- f"Testpoint stage { self .stage } is invalid:\n { self } \n Legal values: Testpoint.stages"
182- )
183- raise ValueError (
184- msg ,
255+ f"The dictionary defining the testpoint named { self .name } "
256+ f"defined a field called { field_name } , but this name is "
257+ "reserved for the tooling."
185258 )
186-
187- # "tests" key must be list.
188- if not isinstance (self .tests , list ):
189- msg = f"'tests' key in { self } is not a list."
190259 raise ValueError (msg )
191260
192261 @staticmethod
0 commit comments