// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0

// verilog_format: off
`define stop $stop
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d:  got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d:  got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
// verilog_format: on

typedef enum bit [1:0] { TXN_READ, TXN_WRITE } txn_e;

// Each constraint exercises one RHS shape allowed by IEEE 1800-2023 18.7.2
// (constraint_set on the right of the implication operator -> ).  `mode`
// selects which constraint contributes; the others are vacuous.
class Forms;
  rand bit [3:0] mode;
  rand bit [3:0] a;
  rand bit [3:0] b;
  rand bit [3:0] c;
  rand bit [31:0] address;
  rand txn_e txn_type;
  rand bit [7:0] arr [4];
  rand bit [3:0] uarr [3];

  // Bare expression (legacy form, supported pre-PR via expr->expr).
  constraint c_expr {
    (mode == 4'd0) -> b == 4'h9;
  }

  // Brace-block: the exact shape from issue #7300.
  constraint c_brace_repro {
    (mode == 4'd1) -> {
      if (txn_type inside {TXN_READ}) address % (1 << 4) == 0;
    }
  }

  // Brace-block with multiple statements.
  constraint c_brace_multi {
    (mode == 4'd2) -> {
      address[0]  == 1'b0;
      address[31] == 1'b0;
    }
  }

  // Bare if (without surrounding braces).
  constraint c_if {
    (mode == 4'd3) -> if (a == 4'h1) b == 4'h7;
  }

  // Bare if/else.
  constraint c_if_else {
    (mode == 4'd4) -> if (a == 4'h1) b == 4'ha;
                      else            b == 4'hb;
  }

  // Bare foreach.
  constraint c_foreach {
    (mode == 4'd5) -> foreach (arr[i]) arr[i] < 8'h40;
  }

  // Bare unique (uses the static-array form V3Randomize supports today).
  constraint c_unique {
    (mode == 4'd6) -> unique { uarr };
  }

  // Bare soft.
  constraint c_soft {
    (mode == 4'd7) -> soft b == 4'hd;
  }

  // Nested implication inside a brace block.
  constraint c_nested {
    (mode == 4'd8) -> {
      (a == 4'h0) -> { b == 4'h5; }
    }
  }
endclass

// Conditional `disable soft` is meta-level: V3Randomize hoists it into a
// runtime AstIf attached to the setup task so the directive only fires
// when its outer condition is true.  When override_flag==0, the soft
// `x == 4'h5` wins; when override_flag==1, the implication fires the
// disable AND the override `x == 4'hc`, so x must be 4'hc.
class DisSoft;
  bit override_flag;
  rand bit [3:0] x;
  constraint c_soft_x { soft x == 4'h5; }
  constraint c_override {
    (override_flag == 1'b1) -> disable soft x ;
    (override_flag == 1'b1) -> x == 4'hc ;
  }
endclass

// MemberSel target for `disable soft`.  Cross-object reference (inner.y)
// parses as AstMemberSel; disable-soft must extract the variable name from
// that node shape too.
class Inner;
  rand bit [3:0] y;
endclass
class DisSoftMember;
  bit override_flag;
  rand Inner inner;
  constraint c_soft_y { soft inner.y == 4'h5; }
  constraint c_override {
    (override_flag == 1'b1) -> disable soft inner.y ;
    (override_flag == 1'b1) -> inner.y == 4'hc ;
  }
  function new(); inner = new(); endfunction
endclass

// `disable soft` in BOTH then and else arms of a single if.  Exercises the
// hoist-list concat path in the else branch (a then-arm hoist already
// exists when the else-arm hoist is appended).  We only check that the
// hard constraint in each arm fires correctly under its `pick` condition;
// soft enforcement priority is not asserted (Verilator's soft solver is
// best-effort).  The condition variable `pick` is non-rand so the setup
// task sees its current value before the solver runs.
class DisSoftBothArms;
  bit [1:0] pick;
  rand bit [3:0] a;
  rand bit [3:0] b;
  constraint c_soft_a { soft a == 4'h7; }
  constraint c_soft_b { soft b == 4'h8; }
  constraint c_split {
    if (pick == 2'd0) { disable soft a; a == 4'h1; }
    else              { disable soft b; b == 4'h2; }
  }
endclass

module t;
  Forms obj;
  DisSoft ds;
  int ok;

  initial begin
    obj = new();

    repeat (10) begin
      ok = obj.randomize() with { mode == 4'd0; };
      `checkd(ok, 1);
      `checkh(obj.b, 4'h9);
    end

    repeat (10) begin
      ok = obj.randomize() with { mode == 4'd1; txn_type == TXN_READ; };
      `checkd(ok, 1);
      `checkh(obj.address[3:0], 4'h0);
    end

    repeat (10) begin
      ok = obj.randomize() with { mode == 4'd2; };
      `checkd(ok, 1);
      `checkh(obj.address[0], 1'b0);
      `checkh(obj.address[31], 1'b0);
    end

    repeat (10) begin
      ok = obj.randomize() with { mode == 4'd3; a == 4'h1; };
      `checkd(ok, 1);
      `checkh(obj.b, 4'h7);
    end

    repeat (10) begin
      ok = obj.randomize() with { mode == 4'd4; a == 4'h1; };
      `checkd(ok, 1);
      `checkh(obj.b, 4'ha);
    end
    repeat (10) begin
      ok = obj.randomize() with { mode == 4'd4; a == 4'h2; };
      `checkd(ok, 1);
      `checkh(obj.b, 4'hb);
    end

    repeat (10) begin
      ok = obj.randomize() with { mode == 4'd5; };
      `checkd(ok, 1);
      foreach (obj.arr[i]) `checkh(obj.arr[i] < 8'h40, 1'b1);
    end

    repeat (10) begin
      ok = obj.randomize() with { mode == 4'd6; };
      `checkd(ok, 1);
      `checkh(obj.uarr[0] == obj.uarr[1], 1'b0);
      `checkh(obj.uarr[0] == obj.uarr[2], 1'b0);
      `checkh(obj.uarr[1] == obj.uarr[2], 1'b0);
    end

    repeat (10) begin
      ok = obj.randomize() with { mode == 4'd7; };
      `checkd(ok, 1);
      `checkh(obj.b, 4'hd);
    end

    repeat (10) begin
      ok = obj.randomize() with { mode == 4'd8; a == 4'h0; };
      `checkd(ok, 1);
      `checkh(obj.b, 4'h5);
    end

    ds = new();
    ds.override_flag = 1'b0;
    repeat (10) begin
      ok = ds.randomize();
      `checkd(ok, 1);
      `checkh(ds.x, 4'h5);
    end
    ds.override_flag = 1'b1;
    repeat (10) begin
      ok = ds.randomize();
      `checkd(ok, 1);
      `checkh(ds.x, 4'hc);
    end

    begin
      automatic DisSoftMember dsm = new();
      dsm.override_flag = 1'b0;
      repeat (10) begin
        ok = dsm.randomize();
        `checkd(ok, 1);
        `checkh(dsm.inner.y, 4'h5);
      end
      dsm.override_flag = 1'b1;
      repeat (10) begin
        ok = dsm.randomize();
        `checkd(ok, 1);
        `checkh(dsm.inner.y, 4'hc);
      end
    end

    begin
      automatic DisSoftBothArms dba = new();
      // pick==0: hard a==1 fires (then arm); hoisted disable_soft(a) runs
      dba.pick = 2'd0;
      repeat (10) begin
        ok = dba.randomize();
        `checkd(ok, 1);
        `checkh(dba.a, 4'h1);
      end
      // pick==1: hard b==2 fires (else arm); hoisted disable_soft(b) runs
      dba.pick = 2'd1;
      repeat (10) begin
        ok = dba.randomize();
        `checkd(ok, 1);
        `checkh(dba.b, 4'h2);
      end
    end

    $write("*-* All Finished *-*\n");
    $finish;
  end
endmodule
