Explained with substitution:
S1 = (S0 & ~X) | (S1 & ~S0 & X) sub S0 with X
S1 = ((X) & ~X) | (S1 & ~(X) & X) X & ~X == 0
S1 = ( 0 ) | ( S1 & 0 ) S1 & 0 == 0;
S1 = ( 0 ) | ( 0 )
S1 = 0
Since the assignment of S1 dependent on its current value, it is considered asynchronous feedback logic. This is normally something you don't want to do. I believe the real equation you want is:
S1 = (Q0 & ~X) | (Q1 & ~Q0 & X)
This makes the code synchronous and predictable. Q1 and Q0 are the previous clocked values of S1 and S0 respectively.
Also, it is important to use non-blocking assignments when assigning (<=
) flops. Verilog is a non-determent simulator. This means operations scheduled in the same region can happen in any order. Using non-blocking on a flop moves the assignment to the NBA region while its evaluation in kept in the active region.
always @(posedge clk)
Q <= D;