[aes/rtl] Implement MixColumns operation
diff --git a/hw/ip/aes/aes.core b/hw/ip/aes/aes.core
index 20db757..32cab00 100644
--- a/hw/ip/aes/aes.core
+++ b/hw/ip/aes/aes.core
@@ -18,6 +18,9 @@
       - rtl/aes_sbox_lut.sv
       - rtl/aes_shift_rows.sv
       - rtl/aes_mix_columns.sv
+      - rtl/aes_mix_single_column.sv
+      - rtl/aes_mul2.sv
+      - rtl/aes_mul4.sv
       - rtl/aes_key_expand.sv
       - rtl/aes_control.sv
       - rtl/aes.sv
diff --git a/hw/ip/aes/rtl/aes_mix_columns.sv b/hw/ip/aes/rtl/aes_mix_columns.sv
index 715ced0..0541649 100644
--- a/hw/ip/aes/rtl/aes_mix_columns.sv
+++ b/hw/ip/aes/rtl/aes_mix_columns.sv
@@ -10,11 +10,13 @@
   output logic [7:0]     data_o [16]
 );
 
-import aes_pkg::*;
-
-// dummy only
-mode_e unused_mode;
-assign unused_mode = mode_i;
-assign data_o      = data_i;
+  // Individually mix columns
+  for (genvar i = 0; i < 4; i++) begin : gen_mix_column
+    aes_mix_single_column aes_mix_column_i (
+      .mode_i ( mode_i            ),
+      .data_i ( data_i[4*i:4*i+3] ),
+      .data_o ( data_o[4*i:4*i+3] )
+    );
+  end
 
 endmodule
diff --git a/hw/ip/aes/rtl/aes_mix_single_column.sv b/hw/ip/aes/rtl/aes_mix_single_column.sv
new file mode 100644
index 0000000..345c66b
--- /dev/null
+++ b/hw/ip/aes/rtl/aes_mix_single_column.sv
@@ -0,0 +1,77 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// AES MixColumns for one single column of the state matrix
+//
+// For details, see Equations 4-7 of:
+// Satoh et al., "A Compact Rijndael Hardware Architecture with S-Box Optimization"
+
+module aes_mix_single_column (
+  input  aes_pkg::mode_e mode_i,
+  input  logic [7:0]     data_i [4],
+  output logic [7:0]     data_o [4]
+);
+
+  import aes_pkg::*;
+
+  logic [7:0] x[4];
+  logic [7:0] y[2];
+  logic [7:0] z[2];
+
+  logic [7:0] x_mul2[4];
+  logic [7:0] y_pre_mul4[2];
+  logic [7:0] y2, y2_pre_mul2;
+
+  logic [7:0] z_muxed[2];
+
+  // Drive x
+  assign x[0] = data_i[0] ^ data_i[3];
+  assign x[1] = data_i[3] ^ data_i[2];
+  assign x[2] = data_i[2] ^ data_i[1];
+  assign x[3] = data_i[1] ^ data_i[0];
+
+  // Mul2 blocks for x
+  for (genvar i = 0; i < 4; i++) begin : gen_x_mul2
+    aes_mul2 x_mul2_i (
+      .data_i ( x[i]      ),
+      .data_o ( x_mul2[i] )
+    );
+  end
+
+  // Drive y_pre_mul4
+  assign y_pre_mul4[0] = data_i[3] ^ data_i[1];
+  assign y_pre_mul4[1] = data_i[2] ^ data_i[0];
+
+  // Mul4 blocks for y
+  for (genvar i = 0; i < 2; i++) begin : gen_mul4
+    aes_mul4 y_mul4_i (
+      .data_i ( y_pre_mul4[i] ),
+      .data_o ( y[i]          )
+    );
+  end
+
+  // Drive y2_pre_mul2
+  assign y2_pre_mul2 = y[0] ^ y[1];
+
+  // Mul2 block for y
+  aes_mul2 y_mul2 (
+    .data_i ( y2_pre_mul2 ),
+    .data_o ( y2          )
+  );
+
+  // Drive z
+  assign z[0] = y2 ^ y[0];
+  assign z[1] = y2 ^ y[1];
+
+  // Mux z
+  assign z_muxed[0] = (mode_i == AES_ENC) ? 8'b0 : z[0];
+  assign z_muxed[1] = (mode_i == AES_ENC) ? 8'b0 : z[1];
+
+  // Drive outputs
+  assign data_o[0] = data_i[1] ^ x_mul2[3] ^ x[1] ^ z_muxed[1];
+  assign data_o[1] = data_i[0] ^ x_mul2[2] ^ x[1] ^ z_muxed[0];
+  assign data_o[2] = data_i[3] ^ x_mul2[1] ^ x[3] ^ z_muxed[1];
+  assign data_o[3] = data_i[2] ^ x_mul2[0] ^ x[3] ^ z_muxed[0];
+
+endmodule
diff --git a/hw/ip/aes/rtl/aes_mul2.sv b/hw/ip/aes/rtl/aes_mul2.sv
new file mode 100644
index 0000000..dc19cd0
--- /dev/null
+++ b/hw/ip/aes/rtl/aes_mul2.sv
@@ -0,0 +1,21 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// AES mul2 implements a constant multiplication by 2 on GF(2^8).
+
+module aes_mul2 (
+  input  logic [7:0] data_i,
+  output logic [7:0] data_o
+);
+
+  assign data_o[7] = data_i[6];
+  assign data_o[6] = data_i[5];
+  assign data_o[5] = data_i[4];
+  assign data_o[4] = data_i[3] ^ data_i[7];
+  assign data_o[3] = data_i[2] ^ data_i[7];
+  assign data_o[2] = data_i[1];
+  assign data_o[1] = data_i[0] ^ data_i[7];
+  assign data_o[0] = data_i[7];
+
+endmodule
diff --git a/hw/ip/aes/rtl/aes_mul4.sv b/hw/ip/aes/rtl/aes_mul4.sv
new file mode 100644
index 0000000..b43ca63
--- /dev/null
+++ b/hw/ip/aes/rtl/aes_mul4.sv
@@ -0,0 +1,21 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// AES mul4 implements a constant multiplication by 4 on GF(2^8).
+
+module aes_mul4 (
+  input  logic [7:0] data_i,
+  output logic [7:0] data_o
+);
+
+  assign data_o[7] = data_i[5];
+  assign data_o[6] = data_i[4];
+  assign data_o[5] = data_i[3] ^ data_i[7];
+  assign data_o[4] = data_i[2] ^ data_i[7] ^ data_i[6];
+  assign data_o[3] = data_i[1]             ^ data_i[6];
+  assign data_o[2] = data_i[0] ^ data_i[7];
+  assign data_o[1] = data_i[7]             ^ data_i[6];
+  assign data_o[0] = data_i[6];
+
+endmodule