/* the interleaving is emulated in the system process by permit variables */

`define N 2

/* to emulate program counter */
typedef enum { L1, L2, L3, L4, L5, L6, L7, L8, 
               L9, L10, L11, L12, L13, L14, L15, L16 } loc;

/* state of how hard a process want to enter critical section */
typedef enum { idle, want_in, in_cs } flag_type;

module system (clk);
input clk;

/* flag? and turn are shared memory */
flag_type reg flag0, flag1;
reg turn;

flag_type wire out_flag0, out_flag1;
wire out_turn0, out_turn1;

wire perm0, perm1;
reg st_perm0, st_perm1; 
// st_pterm? just show which process is chosen 

/* the current processes */
process p0(clk, perm0, flag0, flag1, out_flag0, turn, out_turn0, 0);
process p1(clk, perm1, flag0, flag1, out_flag1, turn, out_turn1, 1);

/* the processes are chosen to be executed by CPU nondeterministically */
assign perm0 = $NDset(@(posedge clk), 0, 1);
assign perm1 = ~perm0;

/* only OS/CPU can update system memory */
initial begin flag0=idle; flag1=idle; turn=0; end
initial st_perm0 = $ND(0,1);
initial st_perm1 = $ND(0,1);
always @(posedge clk) begin
    st_perm0 = perm0; st_perm1 = perm1;
    if (perm0==1)     begin flag0 = out_flag0; turn = out_turn0; end
    else if(perm1==1) begin flag1 = out_flag1; turn = out_turn1; end
end

endmodule


/* individual process */
module process (clk, perm, flag0, flag1, out_flag, turn, out_turn, i);
input clk;
input perm;
input flag0, flag1;
output out_flag;
input turn;
output out_turn;
input i;

flag_type wire flag0, flag1;
flag_type wire out_flag;
 
// pc and j are local to each process
loc reg pc;
reg j;

flag_type wire flagj, flagturn;
wire nond_exit;

assign flagj = (j==0) ?       flag0 : flag1,
       flagturn = (turn==0) ? flag0 : flag1;

/* if the process decide to change system wide data */
assign out_flag = (pc==L1) ? want_in : 
                  (pc==L7) ? in_cs : 
                  (pc==L16)? idle :
                  (i==0) ? flag0 : flag1;
assign out_turn = (pc==L11) ? i : (pc==L15) ? j : turn;
assign nond_exit = $NDset(@(posedge clk), 0, 1);

initial begin j=0; pc=L1; end

// ----- the program is executed forever -----
always @(posedge clk) begin                                            
    if (perm==1) begin
        case (pc)
// repeat   flag[i]=want_in;
            L1: begin pc=L2; end                                       
//     j=turn;
            L2: begin j=turn; pc=L3; end                               
//     while (j != i)
            L3: begin if (j!=i) pc=L4; else pc=L7; end                 
//         do if (flag[j] != idle)
            L4: begin if (flagj != idle) pc=L5; else pc=L6; end        
//                then j = turn;
            L5: begin j=turn; pc=L3; end                               
//                else j = j+1 mod `N;
            L6: begin if (j==`N-1) j=0; else j=j+1; pc=L3; end         
//     flag[i] = in_cs;
            L7: begin pc=L8; end                                       
//     j=0;
            L8: begin j=0; pc=L9; end                                  
//     while ((j<`N) && (j==i || flag[j] != in_cs)) do j=j+1;
            L9: begin                                                  
                if ((j<`N) && ((j==i) || (flagj!=in_cs)))       
                    begin j=j+1; pc=L9; end
                else pc=L10;
                end
// until (j>=`N) && ((turn==i) || flag[turn] == idle);
            L10: begin                                                 
                 if ((j>=`N) && ((turn==i) || (flagturn==idle)))
                     pc=L11;
                 else 
                     pc=L1;
                 end
// turn = i;
            L11: begin pc=L12; end                                     
// critical section
	    
// ----- CRITICAL SECTION -----
            L12: begin if (nond_exit==1) pc=L13; else pc=L12; end      
// j = turn+1 mod `N;
            L13: begin if (turn==`N-1) j=0; else j=turn+1; pc=L14; end 
// while (flag[j] == idle) do j=j+1 mod `N;
            L14: begin if (flagj==idle)                                
                           begin
                           if (j==`N-1) j=0;
                           else j=j+1;
                           pc=L14;
                           end
                       else pc=L15;
                 end
// turn = j;
            L15: begin pc=L16; end                                     
// flag[i] = idle;
            L16: begin if (nond_exit==1) pc=L1; else pc=L16; end       
                                                                       
// ----- REMAINDER SECTION -----
            default: ;
        endcase
    end
end

endmodule
