@@ -469,3 +469,160 @@ fn comb_loop_generic_interface_enabled_specialization_retains_feedthrough() {
469469fn comb_loop_generic_interface_disabled_specialization_has_no_feedthrough ( ) {
470470 assert_interface_comb_loop ( & specialized_interface_code ( 2 ) , false ) ;
471471}
472+
473+ fn formal_modport_connect_code ( source_assignments : & str ) -> String {
474+ format ! (
475+ r#"
476+ interface Bus {{
477+ var request : logic;
478+ var response: logic;
479+ modport initiator {{
480+ request : output,
481+ response: input,
482+ }}
483+ modport target {{
484+ ..converse(initiator)
485+ }}
486+ }}
487+ module Bridge (
488+ producer: modport Bus::initiator,
489+ consumer: modport Bus::target,
490+ ) {{
491+ connect producer <> consumer;
492+ }}
493+ module Top {{
494+ inst producer: Bus;
495+ inst consumer: Bus;
496+ inst bridge: Bridge (
497+ producer: producer,
498+ consumer: consumer,
499+ );
500+ {source_assignments}
501+ }}
502+ "#
503+ )
504+ }
505+
506+ #[ test]
507+ fn comb_loop_formal_modport_connect_detects_feedback ( ) {
508+ assert_interface_comb_loop (
509+ & formal_modport_connect_code (
510+ r#"assign consumer.request = consumer.response;
511+ assign producer.response = producer.request;"# ,
512+ ) ,
513+ true ,
514+ ) ;
515+ }
516+
517+ #[ test]
518+ fn comb_loop_formal_modport_connect_with_constant_sources_has_no_feedback ( ) {
519+ assert_interface_comb_loop (
520+ & formal_modport_connect_code (
521+ r#"assign consumer.request = 0;
522+ assign producer.response = 0;"# ,
523+ ) ,
524+ false ,
525+ ) ;
526+ }
527+
528+ fn procedural_modport_connect_code ( overrides : & str ) -> String {
529+ format ! (
530+ r#"
531+ interface Bus {{
532+ var request : logic;
533+ var response: logic;
534+ modport initiator {{
535+ request : output,
536+ response: input,
537+ }}
538+ modport target {{
539+ ..converse(initiator)
540+ }}
541+ }}
542+ module Top {{
543+ inst producer: Bus;
544+ inst consumer: Bus;
545+ always_comb {{
546+ producer.initiator <> consumer.target;
547+ {overrides}
548+ }}
549+ assign consumer.request = consumer.response;
550+ assign producer.response = producer.request;
551+ }}
552+ "#
553+ )
554+ }
555+
556+ #[ test]
557+ fn comb_loop_procedural_modport_connect_detects_feedback ( ) {
558+ assert_interface_comb_loop ( & procedural_modport_connect_code ( "" ) , true ) ;
559+ }
560+
561+ #[ test]
562+ #[ ignore = "comb-loop migration: false positive; procedural modport connect killed by later overrides" ]
563+ fn comb_loop_procedural_modport_connect_overrides_kill_feedback ( ) {
564+ assert_interface_comb_loop (
565+ & procedural_modport_connect_code (
566+ r#"producer.request = 0;
567+ consumer.response = 0;"# ,
568+ ) ,
569+ false ,
570+ ) ;
571+ }
572+
573+ fn partial_modport_connect_code ( source_assignments : & str ) -> String {
574+ format ! (
575+ r#"
576+ interface Bus {{
577+ var request : logic;
578+ var response: logic;
579+ modport initiator {{
580+ request : output,
581+ response: input,
582+ }}
583+ modport target_request {{
584+ request: input,
585+ }}
586+ }}
587+ module RequestBridge (
588+ producer: modport Bus::initiator,
589+ consumer: modport Bus::target_request,
590+ ) {{
591+ connect producer <> consumer;
592+ }}
593+ module Top {{
594+ inst producer: Bus;
595+ inst consumer: Bus;
596+ inst bridge: RequestBridge (
597+ producer: producer,
598+ consumer: consumer,
599+ );
600+ {source_assignments}
601+ }}
602+ "#
603+ )
604+ }
605+
606+ #[ test]
607+ fn comb_loop_partial_modport_connect_detects_shared_member_feedback ( ) {
608+ assert_interface_comb_loop (
609+ & partial_modport_connect_code (
610+ r#"assign consumer.request = producer.response;
611+ assign producer.response = producer.request;
612+ assign consumer.response = 0;"# ,
613+ ) ,
614+ true ,
615+ ) ;
616+ }
617+
618+ #[ test]
619+ fn comb_loop_partial_modport_connect_does_not_connect_absent_member ( ) {
620+ assert_interface_comb_loop (
621+ & partial_modport_connect_code (
622+ r#"assign consumer.request = 0;
623+ assign consumer.response = producer.request;
624+ assign producer.response = consumer.response;"# ,
625+ ) ,
626+ false ,
627+ ) ;
628+ }
0 commit comments