@@ -133,22 +133,23 @@ fn lint_sensitivity_list(
133133 return vec ! [ ] ;
134134 }
135135
136- let mut signals_in_sensitivity_list: FnvHashMap < _ , _ > = names
136+ let signals_in_sensitivity_list: FnvHashMap < _ , _ > = names
137137 . iter ( )
138138 . flat_map ( |name| {
139139 name. item
140- . get_suffix_reference ( )
140+ . get_suffix_reference_disregard_index ( )
141141 . map ( |reference| ( reference, name. span ) )
142142 } )
143143 . collect ( ) ;
144- let required_sensitivity_list =
145- generate_sensitivity_list ( root, process, signals_in_sensitivity_list. clone ( ) , ctx) ;
146- let mut missing_signals = Vec :: new ( ) ;
147- for ( eid, src_pos) in required_sensitivity_list {
148- if signals_in_sensitivity_list. remove ( & eid) . is_none ( ) {
149- missing_signals. push ( ( eid, src_pos) )
150- }
151- }
144+ let mut searcher = SensitivityListChecker {
145+ root,
146+ sensitivity_list : signals_in_sensitivity_list. clone ( ) ,
147+ superfluous_entities : signals_in_sensitivity_list,
148+ found_entities : FnvHashMap :: default ( ) ,
149+ } ;
150+ let _ = process. statements . search ( ctx, & mut searcher) ;
151+
152+ let mut missing_signals = searcher. found_entities . into_iter ( ) . collect_vec ( ) ;
152153 let mut diagnostics = Vec :: new ( ) ;
153154 if !missing_signals. is_empty ( ) {
154155 missing_signals. sort_by ( |( _, pos1) , ( _, pos2) | pos1. cmp ( pos2) ) ;
@@ -160,7 +161,7 @@ fn lint_sensitivity_list(
160161 pluralize( missing_signals. len( ) , "The signal" , "Signals" ) ,
161162 missing_signals
162163 . iter( )
163- . map( |( sig, _) | root. get_ent( * sig) . designator. to_string ( ) )
164+ . map( |( sig, _) | format! ( "'{}'" , root. get_ent( * sig) . designator) )
164165 . join( ", " ) ,
165166 pluralize( missing_signals. len( ) , "is" , "are" ) ,
166167 ) ,
@@ -169,13 +170,16 @@ fn lint_sensitivity_list(
169170 for ( signal, pos) in missing_signals {
170171 diag. add_related (
171172 pos,
172- format ! ( "{} first read here" , root. get_ent( signal) . describe( ) ) ,
173+ format ! (
174+ "signal '{}' first read here" ,
175+ root. get_ent( signal) . designator
176+ ) ,
173177 )
174178 }
175179 diagnostics. push ( diag) ;
176180 }
177181
178- for ( _, pos) in signals_in_sensitivity_list {
182+ for ( _, pos) in searcher . superfluous_entities {
179183 diagnostics. push ( Diagnostic :: new (
180184 pos. get_pos ( ctx) ,
181185 "Signal is never read in the process" ,
@@ -236,41 +240,18 @@ fn pluralize<'a>(len: usize, singular: &'a str, plural: &'a str) -> &'a str {
236240 }
237241}
238242
239- /// finds all signals that are present in a sensitivity list,
240- /// if the process contains the `all` keyword inside the sensitivity list.
241- /// This is also used to compare against the actual values of the sensitivity list
242- /// so that superfluous / lacking signals can be detected.
243- ///
244- /// Caveats:
245- /// - Information from extended names is not included
246- /// - Information from procedure calls is not included
247- struct SensitivityListGenerator < ' a > {
243+ struct SensitivityListChecker < ' a > {
248244 root : & ' a DesignRoot ,
249245 /// The sensitivity list present at the generate statement
250246 sensitivity_list : FnvHashMap < EntityId , TokenSpan > ,
247+ /// Additional entities that are in the Sensitivity list, but are never read in the process.
248+ superfluous_entities : FnvHashMap < EntityId , TokenSpan > ,
251249 /// A set of named entities that form the sensitivity list.
252250 /// Additionally, the token span of the first occurrence is also provided.
253251 found_entities : FnvHashMap < EntityId , SrcPos > ,
254252}
255253
256- /// Generate a sensitivity list from a process statement
257- fn generate_sensitivity_list (
258- root : & DesignRoot ,
259- process : & ProcessStatement ,
260- sensitivity_list : FnvHashMap < EntityId , TokenSpan > ,
261- ctx : & dyn TokenAccess ,
262- ) -> FnvHashMap < EntityId , SrcPos > {
263- let mut searcher = SensitivityListGenerator {
264- root,
265- sensitivity_list,
266- found_entities : FnvHashMap :: default ( ) ,
267- } ;
268- let _ = process. statements . search ( ctx, & mut searcher) ;
269-
270- searcher. found_entities
271- }
272-
273- impl SensitivityListGenerator < ' _ > {
254+ impl SensitivityListChecker < ' _ > {
274255 fn analyze_expression ( & mut self , expr : & Expression , span : TokenSpan , ctx : & dyn TokenAccess ) {
275256 match expr {
276257 Expression :: Binary ( _, lhs, rhs) => {
@@ -295,7 +276,7 @@ impl SensitivityListGenerator<'_> {
295276 Expression :: Qualified ( qual_expr) => {
296277 self . analyze_expression ( & qual_expr. expr . item , qual_expr. expr . span , ctx)
297278 }
298- Expression :: Name ( name) => self . analyze_name ( name, span, ctx) ,
279+ Expression :: Name ( name) => self . analyze_name ( name, span, ctx, false ) ,
299280 Expression :: New ( allocator) => match & allocator. item {
300281 Allocator :: Qualified ( qual_expr) => {
301282 self . analyze_expression ( & qual_expr. expr . item , qual_expr. expr . span , ctx)
@@ -307,21 +288,27 @@ impl SensitivityListGenerator<'_> {
307288 }
308289 }
309290
310- fn analyze_name ( & mut self , name : & Name , pos : TokenSpan , ctx : & dyn TokenAccess ) {
291+ fn analyze_name (
292+ & mut self ,
293+ name : & Name ,
294+ pos : TokenSpan ,
295+ ctx : & dyn TokenAccess ,
296+ remove_only : bool ,
297+ ) {
311298 use Name :: * ;
312299 match name {
313300 Designator ( designator) => {
314- self . analyze_designator ( designator, pos, ctx) ;
301+ self . analyze_designator ( designator, pos, ctx, remove_only ) ;
315302 }
316- Selected ( _ , suffix) => {
317- // self.analyze_name(&prefix.item, prefix.span, ctx);
318- self . analyze_designator ( & suffix. item , pos, ctx) ;
303+ Selected ( prefix , suffix) => {
304+ self . analyze_name ( & prefix. item , prefix. span , ctx, true ) ;
305+ self . analyze_designator ( & suffix. item , pos, ctx, remove_only ) ;
319306 }
320- SelectedAll ( prefix) => self . analyze_name ( & prefix. item , prefix. span , ctx) ,
321- Slice ( prefix, _) => self . analyze_name ( & prefix. item , prefix. span , ctx) ,
307+ SelectedAll ( prefix) => self . analyze_name ( & prefix. item , prefix. span , ctx, remove_only ) ,
308+ Slice ( prefix, _) => self . analyze_name ( & prefix. item , prefix. span , ctx, remove_only ) ,
322309 Attribute ( attr) => self . analyze_attribute_name ( attr, ctx) ,
323310 CallOrIndexed ( coi) => {
324- self . analyze_name ( & coi. name . item , coi. name . span , ctx) ;
311+ self . analyze_name ( & coi. name . item , coi. name . span , ctx, remove_only ) ;
325312 for item in & coi. parameters . items {
326313 match & item. actual . item {
327314 ActualPart :: Expression ( expr) => {
@@ -342,17 +329,29 @@ impl SensitivityListGenerator<'_> {
342329 designator : & WithRef < Designator > ,
343330 pos : TokenSpan ,
344331 ctx : & dyn TokenAccess ,
332+ remove_only : bool ,
345333 ) {
346334 if let Some ( reference) = designator. reference . get ( ) {
335+ self . superfluous_entities . remove ( & reference) ;
347336 let ent = self . root . get_ent ( reference) ;
348- if ent. is_signal ( ) {
349- self . sensitivity_list . remove ( & ent. id ) ;
337+ if ent. is_signal ( ) && !self . sensitivity_list . contains_key ( & reference) && !remove_only {
338+ self . found_entities
339+ . entry ( reference)
340+ . or_insert_with ( || pos. get_pos ( ctx) ) ;
350341 }
351342 }
352343 }
353344
354345 fn analyze_attribute_name ( & mut self , attr : & AttributeName , ctx : & dyn TokenAccess ) {
355- self . analyze_name ( & attr. name . item , attr. name . span , ctx) ;
346+ use AttributeDesignator :: * ;
347+ match attr. attr . item {
348+ Ident ( _) | Image => { }
349+ // These likely do not belong inside a sensitivity list.
350+ // However, true analysis can only be performed when it is known whether the name is
351+ // static or not.
352+ _ => return ,
353+ }
354+ self . analyze_name ( & attr. name . item , attr. name . span , ctx, false ) ;
356355 if let Some ( expr) = & attr. expr {
357356 self . analyze_expression ( & expr. item , expr. span , ctx)
358357 }
@@ -411,7 +410,7 @@ impl SensitivityListGenerator<'_> {
411410 fn analyze_discrete_range ( & mut self , range : & DiscreteRange , ctx : & dyn TokenAccess ) {
412411 match range {
413412 DiscreteRange :: Discrete ( name, range) => {
414- self . analyze_name ( & name. item , name. span , ctx) ;
413+ self . analyze_name ( & name. item , name. span , ctx, false ) ;
415414 if let Some ( range) = range {
416415 self . analyze_range ( range, ctx)
417416 }
@@ -460,7 +459,7 @@ impl SensitivityListGenerator<'_> {
460459 }
461460}
462461
463- impl Searcher for SensitivityListGenerator < ' _ > {
462+ impl Searcher for SensitivityListChecker < ' _ > {
464463 fn search_decl ( & mut self , ctx : & dyn TokenAccess , decl : FoundDeclaration < ' _ > ) -> SearchState {
465464 use SequentialStatement :: * ;
466465 if let DeclarationItem :: SequentialStatement ( stmt) = & decl. ast {
@@ -546,29 +545,16 @@ enum ProcessCategory {
546545/// ```
547546/// but won't work for some more exotic, mixed processes.
548547fn get_likely_process_category ( root : & DesignRoot , process : & ProcessStatement ) -> ProcessCategory {
549- // This might be incorrect for weird mixed processes like
550- // ```
551- // process (clk) is
552- // begin
553- // if rising_edge(clk) then
554- // // ...
555- // end if;
556- // a <= b or c;
557- // end process;
558- // ```
559- if process. statements . len ( ) > 1 {
560- return ProcessCategory :: Combinational ;
561- } ;
562- // using iter().all(...) guards against cases like the following:
548+ // using iter().any(...) guards against edge cases like the following:
563549 // ```
564550 // process(clkA, clkB) is
565551 // begin
566552 // if rising_edge(clkA) then
567553 // end if;
568- // if rising_edge(clkB) then
569- // end if ;
554+ //
555+ // some_assignment <= xy ;
570556 // end process;
571- let is_likely_clocked = process. statements . iter ( ) . all ( |stmt| {
557+ let is_likely_clocked = process. statements . iter ( ) . any ( |stmt| {
572558 match & stmt. statement . item {
573559 SequentialStatement :: If ( if_stmt) => {
574560 // this is always guaranteed to be present
@@ -630,23 +616,14 @@ fn is_likely_clocked(root: &DesignRoot, expression: &Expression) -> bool {
630616mod tests {
631617 use super :: * ;
632618 use crate :: analysis:: tests:: { check_no_diagnostics, LibraryBuilder } ;
633- use crate :: syntax:: test:: Code ;
619+ use crate :: syntax:: test:: check_diagnostics ;
634620 use std:: cell:: Cell ;
635621
636- fn get_ent ( root : & DesignRoot , code : Code ) -> ( EntityId , SrcPos ) {
637- (
638- root. search_reference ( code. source ( ) , code. start ( ) )
639- . unwrap ( )
640- . id ,
641- code. pos ( ) ,
642- )
643- }
644-
645622 #[ test]
646623 fn extract_sensitivity_list_no_items ( ) {
647624 let mut builder = LibraryBuilder :: new ( ) ;
648625
649- let _ = builder. code (
626+ builder. code (
650627 "libname" ,
651628 "
652629entity ent is
@@ -662,11 +639,12 @@ end architecture;",
662639
663640 let ( root, diagnostics) = builder. get_analyzed_root ( ) ;
664641 check_no_diagnostics ( & diagnostics) ;
642+
665643 let num_of_searches = Cell :: new ( 0 ) ;
666644 let mut searcher = ProcessSearcher :: new ( |proc, ctx| {
667645 num_of_searches. set ( num_of_searches. get ( ) + 1 ) ;
668- let res = generate_sensitivity_list ( & root, proc , ctx ) ;
669- assert_eq ! ( res , FnvHashMap :: default ( ) ) ;
646+ let diag = lint_sensitivity_list ( & root, ctx , proc ) ;
647+ assert_eq ! ( diag , Vec :: default ( ) ) ;
670648 } ) ;
671649 let _ = root. search ( & mut searcher) ;
672650 assert_eq ! ( num_of_searches. get( ) , 1 )
@@ -688,15 +666,33 @@ end entity;
688666architecture a of ent is
689667 -- all of these signals should be considered as being 'read' in the sensitivity list
690668 signal sig_b, sig_c, sig_d, sig_e, sig_f, sig_g, sig_h, sig_i, sig_j, sig_k, sig_l : bit;
669+ -- all of these signals are fine and present in the sensitivity list
670+ signal sig_0 : bit;
671+ signal sig_1 : bit_vector(1 downto 0);
672+ type t_rec is record
673+ foo : bit;
674+ end record;
675+ signal sig_2 : t_rec;
676+ signal sig_3, sig_4, sig_5, sig_6 : bit;
677+ -- superfluous signal
678+ signal additional : bit;
691679 signal void : bit;
692680
693681 function func(din: bit) return bit is
694682 begin
695683 end func;
696684begin
697- process is
685+ process(sig_0, sig_1(0), sig_2, sig_3, sig_4, sig_5, sig_6, additional) is
698686 variable void_var : bit;
699687 begin
688+ if (sig_5 = '1') then
689+ void <= sig_6;
690+ end if;
691+ void <= sig_3 or sig_4;
692+ void <= sig_3;
693+ void <= sig_2.foo;
694+ void <= sig_0;
695+ void <= sig_1(0);
700696 void <= sig_a;
701697 void <= sig_b;
702698 void <= sig_b;
@@ -720,25 +716,29 @@ end architecture;",
720716 check_no_diagnostics ( & diagnostics) ;
721717
722718 let expected_signals = [
723- get_ent ( & root, code. s ( "sig_a" , 2 ) ) ,
724- get_ent ( & root, code. s ( "sig_b" , 2 ) ) ,
725- get_ent ( & root, code. s ( "sig_c" , 2 ) ) ,
726- get_ent ( & root, code. s ( "sig_d" , 2 ) ) ,
727- get_ent ( & root, code. s ( "sig_e" , 2 ) ) ,
728- get_ent ( & root, code. s ( "sig_f" , 2 ) ) ,
729- get_ent ( & root, code. s ( "sig_g" , 2 ) ) ,
730- get_ent ( & root, code. s ( "sig_h" , 2 ) ) ,
731- get_ent ( & root, code. s ( "sig_i" , 2 ) ) ,
732- get_ent ( & root, code. s ( "sig_j" , 2 ) ) ,
733- get_ent ( & root, code. s ( "sig_k" , 2 ) ) ,
734- get_ent ( & root, code. s ( "sig_l" , 2 ) ) ,
735- ] ;
719+ "sig_a" , "sig_b" , "sig_c" , "sig_d" , "sig_e" , "sig_f" , "sig_g" , "sig_h" , "sig_i" ,
720+ "sig_j" , "sig_k" , "sig_l" ,
721+ ]
722+ . map ( |value| ( value, code. s ( value, 2 ) . pos ( ) ) ) ;
736723
737724 let num_of_searches = Cell :: new ( 0 ) ;
738725 let mut searcher = ProcessSearcher :: new ( |proc, ctx| {
739726 num_of_searches. set ( num_of_searches. get ( ) + 1 ) ;
740- let res = generate_sensitivity_list ( & root, proc, ctx) ;
741- assert_eq ! ( res, expected_signals. iter( ) . cloned( ) . collect( ) ) ;
727+ let res = lint_sensitivity_list ( & root, ctx, proc) ;
728+ let mut expected_missing_diag = Diagnostic :: new (
729+ code. s1 ( "process" ) . pos ( ) ,
730+ "Signals 'sig_a', 'sig_b', 'sig_c', 'sig_d', 'sig_e', 'sig_f', 'sig_g', 'sig_h', 'sig_i', 'sig_j', 'sig_k', 'sig_l' are not read in the sensitivity list" ,
731+ ErrorCode :: MissingInSensitivityList
732+ ) ;
733+ for ( name, pos) in & expected_signals {
734+ expected_missing_diag. add_related ( pos, format ! ( "signal '{name}' first read here" ) ) ;
735+ }
736+ let expected_superfluous_diag = Diagnostic :: new (
737+ code. s ( "additional" , 2 ) ,
738+ "Signal is never read in the process" ,
739+ ErrorCode :: SuperfluousInSensitivityList ,
740+ ) ;
741+ check_diagnostics ( res, vec ! [ expected_missing_diag, expected_superfluous_diag] ) ;
742742 } ) ;
743743 let _ = root. search ( & mut searcher) ;
744744 assert_eq ! ( num_of_searches. get( ) , 1 )
@@ -845,6 +845,6 @@ end architecture;",
845845 idx. set ( idx. get ( ) + 1 ) ;
846846 } ) ;
847847 let _ = root. search ( & mut searcher) ;
848- assert_eq ! ( idx. get( ) , 8 ) ;
848+ assert_eq ! ( idx. get( ) , 9 ) ;
849849 }
850850}
0 commit comments