@@ -460,3 +460,87 @@ def test_method(self):
460460 setattr (target_module , class_name , test_class )
461461
462462 return True
463+
464+
465+ class OverlappingSignalError (Exception ):
466+ """Raised when signals within a command have overlapping bit definitions."""
467+ pass
468+
469+
470+ def check_overlapping_signals (signalset_json : str ) -> List [Dict [str , Any ]]:
471+ """
472+ Check a signalset for overlapping signal bit definitions within commands.
473+
474+ Uses a bitset approach: for each command, track which bits are occupied.
475+ If a signal tries to use a bit that's already occupied, it's an overlap.
476+
477+ Args:
478+ signalset_json: JSON string containing the signal set definition
479+
480+ Returns:
481+ List of overlap errors, each containing:
482+ - command: command identifier (hdr/cmd)
483+ - signal_id: the signal that caused the overlap
484+ - bit: the bit index that was already occupied
485+ - conflicting_signal_id: the signal that already occupied the bit
486+
487+ Raises:
488+ OverlappingSignalError: If any overlapping signals are found
489+ """
490+ import json
491+ signalset = json .loads (signalset_json )
492+
493+ errors = []
494+
495+ for command in signalset .get ('commands' , []):
496+ # Track which bits are occupied and by which signal
497+ occupied_bits : Dict [int , str ] = {}
498+
499+ cmd_identifier = f"hdr={ command .get ('hdr' , '?' )} , cmd={ command .get ('cmd' , '?' )} "
500+
501+ for signal in command .get ('signals' , []):
502+ signal_id = signal .get ('id' , 'unknown' )
503+ fmt = signal .get ('fmt' , {})
504+
505+ start_bit = fmt .get ('bix' , 0 )
506+ bit_length = fmt .get ('len' , 0 )
507+
508+ for bit in range (start_bit , start_bit + bit_length ):
509+ if bit in occupied_bits :
510+ errors .append ({
511+ 'command' : cmd_identifier ,
512+ 'signal_id' : signal_id ,
513+ 'bit' : bit ,
514+ 'conflicting_signal_id' : occupied_bits [bit ]
515+ })
516+ # Only report the first conflicting bit per signal
517+ break
518+ occupied_bits [bit ] = signal_id
519+
520+ if errors :
521+ error_messages = []
522+ for err in errors :
523+ error_messages .append (
524+ f"Signal '{ err ['signal_id' ]} ' overlaps with '{ err ['conflicting_signal_id' ]} ' "
525+ f"at bit { err ['bit' ]} in command [{ err ['command' ]} ]"
526+ )
527+ raise OverlappingSignalError (
528+ f"Found { len (errors )} overlapping signal(s):\n " + "\n " .join (error_messages )
529+ )
530+
531+ return errors
532+
533+
534+ def test_no_overlapping_signals (signalset_json : str ):
535+ """
536+ Test that a signalset has no overlapping signal bit definitions.
537+
538+ This function can be used by other repos to validate their signalset files.
539+
540+ Args:
541+ signalset_json: JSON string containing the signal set definition
542+
543+ Raises:
544+ OverlappingSignalError: If any overlapping signals are found
545+ """
546+ check_overlapping_signals (signalset_json )
0 commit comments