Merge remote-tracking branch 'origin/master' into xc7dsp
[yosys.git] / techlibs / xilinx / cells_sim.v
index 33b2a8f620bf6f13175b74dd3cd4ded2500e3d4e..1a041aac7f5d5b4c1fe2bb135d882ccdeff51ab0 100644 (file)
@@ -29,24 +29,35 @@ module GND(output G);
   assign G = 0;
 endmodule
 
-module IBUF(output O, input I);
+module IBUF(
+    output O,
+    (* iopad_external_pin *)
+    input I);
   parameter IOSTANDARD = "default";
   parameter IBUF_LOW_PWR = 0;
   assign O = I;
 endmodule
 
-module OBUF(output O, input I);
+module OBUF(
+    (* iopad_external_pin *)
+    output O,
+    input I);
   parameter IOSTANDARD = "default";
   parameter DRIVE = 12;
   parameter SLEW = "SLOW";
   assign O = I;
 endmodule
 
-module BUFG(output O, input I);
+module BUFG(
+    (* clkbuf_driver *)
+    output O,
+    input I);
+
   assign O = I;
 endmodule
 
 module BUFGCTRL(
+    (* clkbuf_driver *)
     output O,
     input I0, input I1,
     input S0, input S1,
@@ -72,7 +83,11 @@ assign O = S0_true ? I0_internal : (S1_true ? I1_internal : INIT_OUT);
 
 endmodule
 
-module BUFHCE(output O, input I, input CE);
+module BUFHCE(
+    (* clkbuf_driver *)
+    output O,
+    input I,
+    input CE);
 
 parameter [0:0] INIT_OUT = 1'b0;
 parameter CE_TYPE = "SYNC";
@@ -169,20 +184,20 @@ module MUXF8(output O, input I0, I1, S);
   assign O = S ? I1 : I0;
 endmodule
 
-`ifdef _ABC
-(* abc_box_id = 3, lib_whitebox *)
-module \$__XILINX_MUXF78 (output O, input I0, I1, I2, I3, S0, S1);
-  assign O = S1 ? (S0 ? I3 : I2)
-                : (S0 ? I1 : I0);
-endmodule
-`endif
-
 module XORCY(output O, input CI, LI);
   assign O = CI ^ LI;
 endmodule
 
-(* abc_box_id = 4, abc_carry="CI,CO", lib_whitebox *)
-module CARRY4(output [3:0] CO, O, input CI, CYINIT, input [3:0] DI, S);
+(* abc_box_id = 4, lib_whitebox *)
+module CARRY4(
+  (* abc_carry *)
+  output [3:0] CO,
+  output [3:0] O,
+  (* abc_carry *)
+  input        CI,
+  input        CYINIT,
+  input  [3:0] DI, S
+);
   assign O = S ^ {CO[2:0], CI | CYINIT};
   assign CO[0] = S[0] ? CI | CYINIT : DI[0];
   assign CO[1] = S[1] ? CO[0] : DI[1];
@@ -213,7 +228,15 @@ endmodule
 
 `endif
 
-module FDRE (output reg Q, input C, CE, D, R);
+// Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLL_L.sdf#L238-L250
+
+module FDRE (
+  (* abc_arrival=303 *)
+  output reg Q,
+  (* clkbuf_sink *)
+  input C, 
+  input CE, D, R
+);
   parameter [0:0] INIT = 1'b0;
   parameter [0:0] IS_C_INVERTED = 1'b0;
   parameter [0:0] IS_D_INVERTED = 1'b0;
@@ -225,7 +248,13 @@ module FDRE (output reg Q, input C, CE, D, R);
   endcase endgenerate
 endmodule
 
-module FDSE (output reg Q, input C, CE, D, S);
+module FDSE (
+  (* abc_arrival=303 *)
+  output reg Q,
+  (* clkbuf_sink *)
+  input C,
+  input CE, D, S
+);
   parameter [0:0] INIT = 1'b1;
   parameter [0:0] IS_C_INVERTED = 1'b0;
   parameter [0:0] IS_D_INVERTED = 1'b0;
@@ -237,7 +266,13 @@ module FDSE (output reg Q, input C, CE, D, S);
   endcase endgenerate
 endmodule
 
-module FDCE (output reg Q, input C, CE, D, CLR);
+module FDCE (
+  (* abc_arrival=303 *)
+  output reg Q,
+  (* clkbuf_sink *)
+  input C,
+  input CE, D, CLR
+);
   parameter [0:0] INIT = 1'b0;
   parameter [0:0] IS_C_INVERTED = 1'b0;
   parameter [0:0] IS_D_INVERTED = 1'b0;
@@ -251,7 +286,13 @@ module FDCE (output reg Q, input C, CE, D, CLR);
   endcase endgenerate
 endmodule
 
-module FDPE (output reg Q, input C, CE, D, PRE);
+module FDPE (
+  (* abc_arrival=303 *)
+  output reg Q,
+  (* clkbuf_sink *)
+  input C,
+  input CE, D, PRE
+);
   parameter [0:0] INIT = 1'b1;
   parameter [0:0] IS_C_INVERTED = 1'b0;
   parameter [0:0] IS_D_INVERTED = 1'b0;
@@ -265,34 +306,62 @@ module FDPE (output reg Q, input C, CE, D, PRE);
   endcase endgenerate
 endmodule
 
-module FDRE_1 (output reg Q, input C, CE, D, R);
+module FDRE_1 (
+  (* abc_arrival=303 *)
+  output reg Q,
+  (* clkbuf_sink *)
+  input C,
+  input CE, D, R
+);
   parameter [0:0] INIT = 1'b0;
   initial Q <= INIT;
   always @(negedge C) if (R) Q <= 1'b0; else if(CE) Q <= D;
 endmodule
 
-module FDSE_1 (output reg Q, input C, CE, D, S);
+module FDSE_1 (
+  (* abc_arrival=303 *)
+  output reg Q,
+  (* clkbuf_sink *)
+  input C,
+  input CE, D, S
+);
   parameter [0:0] INIT = 1'b1;
   initial Q <= INIT;
   always @(negedge C) if (S) Q <= 1'b1; else if(CE) Q <= D;
 endmodule
 
-module FDCE_1 (output reg Q, input C, CE, D, CLR);
+module FDCE_1 (
+  (* abc_arrival=303 *)
+  output reg Q,
+  (* clkbuf_sink *)
+  input C,
+  input CE, D, CLR
+);
   parameter [0:0] INIT = 1'b0;
   initial Q <= INIT;
   always @(negedge C, posedge CLR) if (CLR) Q <= 1'b0; else if (CE) Q <= D;
 endmodule
 
-module FDPE_1 (output reg Q, input C, CE, D, PRE);
+module FDPE_1 (
+  (* abc_arrival=303 *)
+  output reg Q,
+  (* clkbuf_sink *)
+  input C,
+  input CE, D, PRE
+);
   parameter [0:0] INIT = 1'b1;
   initial Q <= INIT;
   always @(negedge C, posedge PRE) if (PRE) Q <= 1'b1; else if (CE) Q <= D;
 endmodule
 
-(* abc_box_id = 5, abc_scc_break="D,WE" *)
 module RAM32X1D (
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L957
+  (* abc_arrival=1153 *)
   output DPO, SPO,
-  input  D, WCLK, WE,
+  input  D,
+  (* clkbuf_sink *)
+  input  WCLK,
+  input  WE,
   input  A0, A1, A2, A3, A4,
   input  DPRA0, DPRA1, DPRA2, DPRA3, DPRA4
 );
@@ -307,10 +376,14 @@ module RAM32X1D (
   always @(posedge clk) if (WE) mem[a] <= D;
 endmodule
 
-(* abc_box_id = 6, abc_scc_break="D,WE" *)
 module RAM64X1D (
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L957
+  (* abc_arrival=1153 *)
   output DPO, SPO,
-  input  D, WCLK, WE,
+  input  D,
+  (* clkbuf_sink *)
+  input  WCLK,
+  input  WE,
   input  A0, A1, A2, A3, A4, A5,
   input  DPRA0, DPRA1, DPRA2, DPRA3, DPRA4, DPRA5
 );
@@ -325,10 +398,14 @@ module RAM64X1D (
   always @(posedge clk) if (WE) mem[a] <= D;
 endmodule
 
-(* abc_box_id = 7, abc_scc_break="D,WE" *)
 module RAM128X1D (
-  output       DPO, SPO,
-  input        D, WCLK, WE,
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L957
+  (* abc_arrival=1153 *)
+  output DPO, SPO,
+  input        D,
+  (* clkbuf_sink *)
+  input        WCLK,
+  input        WE,
   input  [6:0] A, DPRA
 );
   parameter INIT = 128'h0;
@@ -341,8 +418,13 @@ module RAM128X1D (
 endmodule
 
 module SRL16E (
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L904-L905
+  (* abc_arrival=1472 *)
   output Q,
-  input A0, A1, A2, A3, CE, CLK, D
+  input A0, A1, A2, A3, CE,
+  (* clkbuf_sink *)
+  input CLK,
+  input D
 );
   parameter [15:0] INIT = 16'h0000;
   parameter [0:0] IS_CLK_INVERTED = 1'b0;
@@ -354,15 +436,44 @@ module SRL16E (
       always @(negedge CLK) if (CE) r <= { r[14:0], D };
     end
     else
-        always @(posedge CLK) if (CE) r <= { r[14:0], D };
+      always @(posedge CLK) if (CE) r <= { r[14:0], D };
+  endgenerate
+endmodule
+
+module SRLC16E (
+  output Q,
+  output Q15,
+  input A0, A1, A2, A3, CE,
+  (* clkbuf_sink *)
+  input CLK,
+  input D
+);
+  parameter [15:0] INIT = 16'h0000;
+  parameter [0:0] IS_CLK_INVERTED = 1'b0;
+
+  reg [15:0] r = INIT;
+  assign Q15 = r[15];
+  assign Q = r[{A3,A2,A1,A0}];
+  generate
+    if (IS_CLK_INVERTED) begin
+      always @(negedge CLK) if (CE) r <= { r[14:0], D };
+    end
+    else
+      always @(posedge CLK) if (CE) r <= { r[14:0], D };
   endgenerate
 endmodule
 
 module SRLC32E (
+  // Max delay from: https://github.com/SymbiFlow/prjxray-db/blob/34ea6eb08a63d21ec16264ad37a0a7b142ff6031/artix7/timings/CLBLM_R.sdf#L904-L905
+  (* abc_arrival=1472 *)
   output Q,
+  (* abc_arrival=1114 *)
   output Q31,
   input [4:0] A,
-  input CE, CLK, D
+  input CE,
+  (* clkbuf_sink *)
+  input CLK,
+  input D
 );
   parameter [31:0] INIT = 32'h00000000;
   parameter [0:0] IS_CLK_INVERTED = 1'b0;
@@ -382,13 +493,13 @@ endmodule
 module DSP48E1 (
     output [29:0] ACOUT,
     output [17:0] BCOUT,
-    output CARRYCASCOUT,
-    output [3:0] CARRYOUT,
-    output MULTSIGNOUT,
+    output reg CARRYCASCOUT,
+    output reg [3:0] CARRYOUT,
+    output reg MULTSIGNOUT,
     output OVERFLOW,
     output reg signed [47:0] P,
-    output PATTERNBDETECT,
-    output PATTERNDETECT,
+    output reg PATTERNBDETECT,
+    output reg PATTERNDETECT,
     output [47:0] PCOUT,
     output UNDERFLOW,
     input signed [29:0] A,
@@ -413,7 +524,7 @@ module DSP48E1 (
     input CEINMODE,
     input CEM,
     input CEP,
-    input CLK,
+    (* clkbuf_sink *) input CLK,
     input [24:0] D,
     input [4:0] INMODE,
     input MULTSIGNIN,
@@ -463,29 +574,10 @@ module DSP48E1 (
 
     initial begin
 `ifdef __ICARUS__
-        if (ACASCREG != 0)          $fatal(1, "Unsupported ACASCREG value");
-        if (ADREG != 0)             $fatal(1, "Unsupported ADREG value");
-        if (ALUMODEREG != 0)        $fatal(1, "Unsupported ALUMODEREG value");
-        if (AREG == 2)              $fatal(1, "Unsupported AREG value");
         if (AUTORESET_PATDET != "NO_RESET") $fatal(1, "Unsupported AUTORESET_PATDET value");
-        if (A_INPUT != "DIRECT")    $fatal(1, "Unsupported A_INPUT value");
-        if (BCASCREG != 0)          $fatal(1, "Unsupported BCASCREG value");
-        if (BREG == 2)              $fatal(1, "Unsupported BREG value");
-        if (B_INPUT != "DIRECT")    $fatal(1, "Unsupported B_INPUT value");
-        if (CARRYINREG != 0)        $fatal(1, "Unsupported CARRYINREG value");
-        if (CARRYINSELREG != 0)     $fatal(1, "Unsupported CARRYINSELREG value");
-        if (CREG != 0)              $fatal(1, "Unsupported CREG value");
-        if (DREG != 0)              $fatal(1, "Unsupported DREG value");
-        if (INMODEREG != 0)         $fatal(1, "Unsupported INMODEREG value");
-        if (MREG != 0)              $fatal(1, "Unsupported MREG value");
-        if (OPMODEREG != 0)         $fatal(1, "Unsupported OPMODEREG value");
-        //if (PREG != 0)              $fatal(1, "Unsupported PREG value");
         if (SEL_MASK != "MASK")     $fatal(1, "Unsupported SEL_MASK value");
         if (SEL_PATTERN != "PATTERN") $fatal(1, "Unsupported SEL_PATTERN value");
-        if (USE_DPORT != "FALSE")   $fatal(1, "Unsupported USE_DPORT value");
-        if (USE_MULT != "MULTIPLY") $fatal(1, "Unsupported USE_MULT value");
-        if (USE_PATTERN_DETECT != "NO_PATDET") $fatal(1, "Unsupported USE_PATTERN_DETECT value");
-        if (USE_SIMD != "ONE48")    $fatal(1, "Unsupported USE_SIMD value");
+        if (USE_SIMD != "ONE48" && USE_SIMD != "TWO24" && USE_SIMD != "FOUR12")    $fatal(1, "Unsupported USE_SIMD value");
         if (IS_ALUMODE_INVERTED != 4'b0) $fatal(1, "Unsupported IS_ALUMODE_INVERTED value");
         if (IS_CARRYIN_INVERTED != 1'b0) $fatal(1, "Unsupported IS_CARRYIN_INVERTED value");
         if (IS_CLK_INVERTED != 1'b0) $fatal(1, "Unsupported IS_CLK_INVERTED value");
@@ -494,34 +586,369 @@ module DSP48E1 (
 `endif
     end
 
-    reg signed [29:0] Ar;
-    reg signed [17:0] Br;
-    reg signed [47:0] Pr;
+    wire signed [29:0] A_muxed;
+    wire signed [17:0] B_muxed;
+
+    generate
+        if (A_INPUT == "CASCADE") assign A_muxed = ACIN;
+        else assign A_muxed = A;
+
+        if (B_INPUT == "CASCADE") assign B_muxed = BCIN;
+        else assign B_muxed = B;
+    endgenerate
+
+    reg signed [29:0] Ar1, Ar2;
+    reg signed [24:0] Dr;
+    reg signed [17:0] Br1, Br2;
+    reg signed [47:0] Cr;
+    reg        [4:0]  INMODEr = 5'b0;
+    reg        [6:0]  OPMODEr = 7'b0;
+    reg        [3:0]  ALUMODEr = 4'b0;
+    reg        [2:0]  CARRYINSELr = 3'b0;
+
+    generate
+        // Configurable A register
+        if (AREG == 2) begin
+            initial Ar1 = 30'b0;
+            initial Ar2 = 30'b0;
+            always @(posedge CLK)
+                if (RSTA) begin
+                    Ar1 <= 30'b0;
+                    Ar2 <= 30'b0;
+                end else begin
+                    if (CEA1) Ar1 <= A_muxed;
+                    if (CEA2) Ar2 <= Ar1;
+                end
+        end else if (AREG == 1) begin
+            //initial Ar1 = 30'b0;
+            initial Ar2 = 30'b0;
+            always @(posedge CLK)
+                if (RSTA) begin
+                    Ar1 <= 30'b0;
+                    Ar2 <= 30'b0;
+                end else begin
+                    if (CEA1) Ar1 <= A_muxed;
+                    if (CEA2) Ar2 <= A_muxed;
+                end
+        end else begin
+            always @* Ar1 <= A_muxed;
+            always @* Ar2 <= A_muxed;
+        end
+
+        // Configurable B register
+        if (BREG == 2) begin
+            initial Br1 = 25'b0;
+            initial Br2 = 25'b0;
+            always @(posedge CLK)
+                if (RSTB) begin
+                    Br1 <= 18'b0;
+                    Br2 <= 18'b0;
+                end else begin
+                    if (CEB1) Br1 <= B_muxed;
+                    if (CEB2) Br2 <= Br1;
+                end
+        end else if (BREG == 1) begin
+            //initial Br1 = 25'b0;
+            initial Br2 = 25'b0;
+            always @(posedge CLK)
+                if (RSTB) begin
+                    Br1 <= 18'b0;
+                    Br2 <= 18'b0;
+                end else begin
+                    if (CEB1) Br1 <= B_muxed;
+                    if (CEB2) Br2 <= B_muxed;
+                end
+        end else begin
+            always @* Br1 <= B_muxed;
+            always @* Br2 <= B_muxed;
+        end
+
+        // C and D registers
+        if (CREG == 1) initial Cr = 48'b0;
+        if (CREG == 1) begin always @(posedge CLK) if (RSTC) Cr <= 48'b0; else if (CEC) Cr <= C; end
+        else           always @* Cr <= C;
+
+        if (CREG == 1) initial Dr = 25'b0;
+        if (DREG == 1) begin always @(posedge CLK) if (RSTD) Dr <= 25'b0; else if (CED) Dr <= D; end
+        else           always @* Dr <= D;
+
+        // Control registers
+        if (INMODEREG == 1) initial INMODEr = 5'b0;
+        if (INMODEREG == 1) begin always @(posedge CLK) if (RSTINMODE) INMODEr <= 5'b0; else if (CEINMODE) INMODEr <= INMODE; end
+        else           always @* INMODEr <= INMODE;
+        if (OPMODEREG == 1) initial OPMODEr = 7'b0;
+        if (OPMODEREG == 1) begin always @(posedge CLK) if (RSTCTRL) OPMODEr <= 7'b0; else if (CECTRL) OPMODEr <= OPMODE; end
+        else           always @* OPMODEr <= OPMODE;
+        if (ALUMODEREG == 1) initial ALUMODEr = 4'b0;
+        if (ALUMODEREG == 1) begin always @(posedge CLK) if (RSTALUMODE) ALUMODEr <= 4'b0; else if (CEALUMODE) ALUMODEr <= ALUMODE; end
+        else           always @* ALUMODEr <= ALUMODE;
+        if (CARRYINSELREG == 1) initial CARRYINSELr = 3'b0;
+        if (CARRYINSELREG == 1) begin always @(posedge CLK) if (RSTCTRL) CARRYINSELr <= 3'b0; else if (CECTRL) CARRYINSELr <= CARRYINSEL; end
+        else           always @* CARRYINSELr <= CARRYINSEL;
+    endgenerate
+
+    // A and B cascade
+    generate
+        if (ACASCREG == 1 && AREG == 2) assign ACOUT = Ar1;
+        else assign ACOUT = Ar2;
+        if (BCASCREG == 1 && BREG == 2) assign BCOUT = Br1;
+        else assign BCOUT = Br2;
+    endgenerate
+
+    // A/D input selection and pre-adder
+    wire signed [29:0] Ar12_muxed = INMODEr[0] ? Ar1 : Ar2;
+    wire signed [24:0] Ar12_gated = INMODEr[1] ? 25'b0 : Ar12_muxed;
+    wire signed [24:0] Dr_gated   = INMODEr[2] ? Dr : 25'b0;
+    wire signed [24:0] AD_result  = INMODEr[3] ? (Dr_gated - Ar12_gated) : (Dr_gated + Ar12_gated);
+    reg  signed [24:0] ADr;
+
     generate
-        if (AREG == 1) begin always @(posedge CLK) if (CEA2) Ar <= A; end
-        else           always @* Ar <= A;
-        if (BREG == 1) begin always @(posedge CLK) if (CEB2) Br <= B; end
-        else           always @* Br <= B;
+        if (ADREG == 1) initial ADr = 25'b0;
+        if (ADREG == 1) begin always @(posedge CLK) if (RSTD) ADr <= 25'b0; else if (CEAD) ADr <= AD_result; end
+        else            always @* ADr <= AD_result;
     endgenerate
 
+    // 25x18 multiplier
+    wire signed [24:0] A_MULT;
+    wire signed [17:0] B_MULT = INMODEr[4] ? Br1 : Br2;
+    generate
+        if (USE_DPORT == "TRUE") assign A_MULT = ADr;
+        else assign A_MULT = Ar12_gated;
+    endgenerate
+
+    wire signed [42:0] M = A_MULT * B_MULT;
+    wire signed [42:0] Mx = (CARRYINSEL == 3'b010) ? 43'bx : M;
+    reg  signed [42:0] Mr = 43'b0;
+
+    // Multiplier result register
+    generate
+        if (MREG == 1) begin always @(posedge CLK) if (RSTM) Mr <= 43'b0; else if (CEM) Mr <= Mx; end
+        else           always @* Mr <= Mx;
+    endgenerate
+
+    wire signed [42:0] Mrx = (CARRYINSELr == 3'b010) ? 43'bx : Mr;
+
+    // X, Y and Z ALU inputs
+    reg signed [47:0] X, Y, Z;
+
     always @* begin
-        Pr <= {48{1'bx}};
+        // X multiplexer
+        case (OPMODEr[1:0])
+            2'b00: X = 48'b0;
+            2'b01: begin X = $signed(Mrx);
+`ifdef __ICARUS__
+                if (OPMODEr[3:2] != 2'b01) $fatal(1, "OPMODEr[3:2] must be 2'b01 when OPMODEr[1:0] is 2'b01");
+`endif
+            end
+            2'b10: begin X = P;
+`ifdef __ICARUS__
+                if (PREG != 1) $fatal(1, "PREG must be 1 when OPMODEr[1:0] is 2'b10");
+`endif
+            end
+            2'b11: X = $signed({Ar2, Br2});
+            default: X = 48'bx;
+        endcase
+
+        // Y multiplexer
+        case (OPMODEr[3:2])
+            2'b00: Y = 48'b0;
+            2'b01: begin Y = 48'b0; // FIXME: more accurate partial product modelling?
+`ifdef __ICARUS__
+                if (OPMODEr[1:0] != 2'b01) $fatal(1, "OPMODEr[1:0] must be 2'b01 when OPMODEr[3:2] is 2'b01");
+`endif
+            end
+            2'b10: Y = {48{1'b1}};
+            2'b11: Y = Cr;
+            default: Y = 48'bx;
+        endcase
+
+        // Z multiplexer
+        case (OPMODEr[6:4])
+            3'b000: Z = 48'b0;
+            3'b001: Z = PCIN;
+            3'b010: begin Z = P;
 `ifdef __ICARUS__
-        if (INMODE != 4'b0000)      $fatal(1, "Unsupported INMODE value");
-        if (ALUMODE != 4'b0000)     $fatal(1, "Unsupported ALUMODE value");
-        if (OPMODE != 7'b000101)    $fatal(1, "Unsupported OPMODE value");
-        if (CARRYINSEL != 3'b000)   $fatal(1, "Unsupported CARRYINSEL value");
-        if (ACIN != 30'b0)          $fatal(1, "Unsupported ACIN value");
-        if (BCIN != 18'b0)          $fatal(1, "Unsupported BCIN value");
-        if (PCIN != 48'b0)          $fatal(1, "Unsupported PCIN value");
-        if (CARRYIN != 1'b0)        $fatal(1, "Unsupported CARRYIN value");
+                if (PREG != 1) $fatal(1, "PREG must be 1 when OPMODEr[6:4] i0s 3'b010");
 `endif
-        Pr[42:0] <= $signed(Ar[24:0]) * Br;
+            end
+            3'b011: Z = Cr;
+            3'b100: begin Z = P;
+`ifdef __ICARUS__
+                if (PREG != 1) $fatal(1, "PREG must be 1 when OPMODEr[6:4] is 3'b100");
+                if (OPMODEr[3:0] != 4'b1000) $fatal(1, "OPMODEr[3:0] must be 4'b1000 when OPMODEr[6:4] i0s 3'b100");
+`endif
+            end
+            3'b101: Z = $signed(PCIN[47:17]);
+            3'b110: Z = $signed(P[47:17]);
+            default: Z = 48'bx;
+        endcase
+    end
+
+    // Carry in
+    wire A24_xnor_B17d = A_MULT[24] ~^ B_MULT[17];
+    reg CARRYINr = 1'b0, A24_xnor_B17 = 1'b0;
+    generate
+        if (CARRYINREG == 1) begin always @(posedge CLK) if (RSTALLCARRYIN) CARRYINr <= 1'b0; else if (CECARRYIN) CARRYINr <= CARRYIN; end
+        else                 always @* CARRYINr = CARRYIN;
+
+        if (MREG == 1) begin always @(posedge CLK) if (RSTALLCARRYIN) A24_xnor_B17 <= 1'b0; else if (CEM) A24_xnor_B17 <= A24_xnor_B17d; end
+        else                 always @* A24_xnor_B17 = A24_xnor_B17d;
+    endgenerate
+
+    reg cin_muxed;
+
+    always @(*) begin
+        case (CARRYINSELr)
+            3'b000: cin_muxed = CARRYINr;
+            3'b001: cin_muxed = ~PCIN[47];
+            3'b010: cin_muxed = CARRYCASCIN;
+            3'b011: cin_muxed = PCIN[47];
+            3'b100: cin_muxed = CARRYCASCOUT;
+            3'b101: cin_muxed = ~P[47];
+            3'b110: cin_muxed = A24_xnor_B17;
+            3'b111: cin_muxed = P[47];
+            default: cin_muxed = 1'bx;
+        endcase
     end
 
+    wire alu_cin = (ALUMODEr[3] || ALUMODEr[2]) ? 1'b0 : cin_muxed;
+
+    // ALU core
+    wire [47:0] Z_muxinv = ALUMODEr[0] ? ~Z : Z;
+    wire [47:0] xor_xyz = X ^ Y ^ Z_muxinv;
+    wire [47:0] maj_xyz = (X & Y) | (X & Z_muxinv) | (Y & Z_muxinv);
+
+    wire [47:0] xor_xyz_muxed = ALUMODEr[3] ? maj_xyz : xor_xyz;
+    wire [47:0] maj_xyz_gated = ALUMODEr[2] ? 48'b0 :  maj_xyz;
+
+    wire [48:0] maj_xyz_simd_gated;
+    wire [3:0] int_carry_in, int_carry_out, ext_carry_out;
+    wire [47:0] alu_sum;
+    assign int_carry_in[0] = 1'b0;
+    wire [3:0] carryout_reset;
+
+    generate
+        if (USE_SIMD == "FOUR12") begin
+            assign maj_xyz_simd_gated = {
+                    maj_xyz_gated[47:36],
+                    1'b0, maj_xyz_gated[34:24],
+                    1'b0, maj_xyz_gated[22:12],
+                    1'b0, maj_xyz_gated[10:0],
+                    alu_cin
+                };
+            assign int_carry_in[3:1] = 3'b000;
+            assign ext_carry_out = {
+                    int_carry_out[3],
+                    maj_xyz_gated[35] ^ int_carry_out[2],
+                    maj_xyz_gated[23] ^ int_carry_out[1],
+                    maj_xyz_gated[11] ^ int_carry_out[0]
+                };
+            assign carryout_reset = 4'b0000;
+        end else if (USE_SIMD == "TWO24") begin
+            assign maj_xyz_simd_gated = {
+                    maj_xyz_gated[47:24],
+                    1'b0, maj_xyz_gated[22:0],
+                    alu_cin
+                };
+            assign int_carry_in[3:1] = {int_carry_out[2], 1'b0, int_carry_out[0]};
+            assign ext_carry_out = {
+                    int_carry_out[3],
+                    1'bx,
+                    maj_xyz_gated[23] ^ int_carry_out[1],
+                    1'bx
+                };
+            assign carryout_reset = 4'b0x0x;
+        end else begin
+            assign maj_xyz_simd_gated = {maj_xyz_gated, alu_cin};
+            assign int_carry_in[3:1] = int_carry_out[2:0];
+            assign ext_carry_out = {
+                    int_carry_out[3],
+                    3'bxxx
+                };
+            assign carryout_reset = 4'b0xxx;
+        end
+
+        genvar i;
+        for (i = 0; i < 4; i = i + 1)
+            assign {int_carry_out[i], alu_sum[i*12 +: 12]} = {1'b0, maj_xyz_simd_gated[i*12 +: ((i == 3) ? 13 : 12)]}
+                                                              + xor_xyz_muxed[i*12 +: 12] + int_carry_in[i];
+    endgenerate
+
+    wire signed [47:0] Pd = ALUMODEr[1] ? ~alu_sum : alu_sum;
+    wire [3:0] CARRYOUTd = (OPMODEr[3:0] == 4'b0101 || ALUMODEr[3:2] != 2'b00) ? 4'bxxxx :
+                           ((ALUMODEr[0] & ALUMODEr[1]) ? ~ext_carry_out : ext_carry_out);
+    wire CARRYCASCOUTd = ext_carry_out[3];
+    wire MULTSIGNOUTd = Mrx[42];
+
+    generate
+        if (PREG == 1) begin
+            initial P = 48'b0;
+            initial CARRYOUT = carryout_reset;
+            initial CARRYCASCOUT = 1'b0;
+            initial MULTSIGNOUT = 1'b0;
+            always @(posedge CLK)
+                if (RSTP) begin
+                    P <= 48'b0;
+                    CARRYOUT <= carryout_reset;
+                    CARRYCASCOUT <= 1'b0;
+                    MULTSIGNOUT <= 1'b0;
+                end else if (CEP) begin
+                    P <= Pd;
+                    CARRYOUT <= CARRYOUTd;
+                    CARRYCASCOUT <= CARRYCASCOUTd;
+                    MULTSIGNOUT <= MULTSIGNOUTd;
+                end
+        end else begin
+            always @* begin
+                P = Pd;
+                CARRYOUT = CARRYOUTd;
+                CARRYCASCOUT = CARRYCASCOUTd;
+                MULTSIGNOUT = MULTSIGNOUTd;
+            end
+        end
+    endgenerate
+
+    assign PCOUT = P;
+
     generate
-        if (PREG == 1) begin always @(posedge CLK) if (CEP) P <= Pr; end
-        else           always @* P <= Pr;
+        wire PATTERNDETECTd, PATTERNBDETECTd;
+
+        if (USE_PATTERN_DETECT == "PATDET") begin
+            // TODO: Support SEL_PATTERN != "PATTERN" and SEL_MASK != "MASK
+            assign PATTERNDETECTd = &(~(Pd ^ PATTERN) | MASK);
+            assign PATTERNBDETECTd = &((Pd ^ PATTERN) | MASK);
+        end else begin
+            assign PATTERNDETECTd = 1'b1;
+            assign PATTERNBDETECTd = 1'b1;
+        end
+
+        if (PREG == 1) begin
+            reg PATTERNDETECTPAST, PATTERNBDETECTPAST;
+            initial PATTERNDETECT = 1'b0;
+            initial PATTERNBDETECT = 1'b0;
+            initial PATTERNDETECTPAST = 1'b0;
+            initial PATTERNBDETECTPAST = 1'b0;
+            always @(posedge CLK)
+                if (RSTP) begin
+                    PATTERNDETECT <= 1'b0;
+                    PATTERNBDETECT <= 1'b0;
+                    PATTERNDETECTPAST <= 1'b0;
+                    PATTERNBDETECTPAST <= 1'b0;
+                end else if (CEP) begin
+                    PATTERNDETECT <= PATTERNDETECTd;
+                    PATTERNBDETECT <= PATTERNBDETECTd;
+                    PATTERNDETECTPAST <= PATTERNDETECT;
+                    PATTERNBDETECTPAST <= PATTERNBDETECT;
+                end
+            assign OVERFLOW = &{PATTERNDETECTPAST, ~PATTERNBDETECT, ~PATTERNDETECT};
+            assign UNDERFLOW = &{PATTERNBDETECTPAST, ~PATTERNBDETECT, ~PATTERNDETECT};
+        end else begin
+            always @* begin
+                PATTERNDETECT = PATTERNDETECTd;
+                PATTERNBDETECT = PATTERNBDETECTd;
+            end
+            assign OVERFLOW = 1'bx, UNDERFLOW = 1'bx;
+        end
     endgenerate
 
 endmodule