diff --git a/platform/altera/Manifest.py b/platform/altera/Manifest.py
index db17cc5a13d43b4be324198edb08c9c27342408a..0e929477fbd79dbc6c13d402d5f62093ed98e43d 100644
--- a/platform/altera/Manifest.py
+++ b/platform/altera/Manifest.py
@@ -1,3 +1,4 @@
 modules = { "local" : [
-  "wb_pcie"
+  "wb_pcie",
+  "flash",
   ]}
diff --git a/platform/altera/flash/Manifest.py b/platform/altera/flash/Manifest.py
new file mode 100644
index 0000000000000000000000000000000000000000..509f1a78a71b5bd60006b9d1fb785c4e3dfff9a7
--- /dev/null
+++ b/platform/altera/flash/Manifest.py
@@ -0,0 +1,5 @@
+files = [ 
+  "altera_spi.vhd",
+  "flash_top.vhd",
+  "altera_flash_pkg.vhd",
+  ]
diff --git a/platform/altera/flash/altera_flash_pkg.vhd b/platform/altera/flash/altera_flash_pkg.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..28ad57cd4b9bbaf1c49b3632c5164aa8086627f7
--- /dev/null
+++ b/platform/altera/flash/altera_flash_pkg.vhd
@@ -0,0 +1,29 @@
+library ieee;
+use ieee.std_logic_1164.all;
+
+library work;
+use work.wishbone_pkg.all;
+
+package altera_flash_pkg is
+
+  component flash_top is
+    generic(
+      -- Sadly, all of this shit must be tuned by hand
+      g_family                 : string;
+      g_port_width             : natural;
+      g_addr_width             : natural;
+      g_input_latch_edge       : std_logic;
+      g_output_latch_edge      : std_logic;
+      g_input_to_output_cycles : natural);
+    port(
+      -- Wishbone interface
+      clk_i     : in  std_logic;
+      rstn_i    : in  std_logic;
+      slave_i   : in  t_wishbone_slave_in;
+      slave_o   : out t_wishbone_slave_out;
+      -- Clock lines for flash chip (might need phase offset)
+      clk_out_i : in  std_logic;
+      clk_in_i  : in  std_logic);
+  end component;
+
+end altera_flash_pkg;
diff --git a/platform/altera/flash/altera_spi.vhd b/platform/altera/flash/altera_spi.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..eef92b5f8abf43eabbad116f94f5f547d10027b6
--- /dev/null
+++ b/platform/altera/flash/altera_spi.vhd
@@ -0,0 +1,308 @@
+library ieee;
+use ieee.std_logic_1164.all;
+
+-- A wrapper for undocumented Altera SPI interface pins
+entity altera_spi is
+  generic(
+    g_family     : string  := "none";
+    g_port_width : natural := 1);
+  port(
+    dclk_i : in  std_logic;
+    ncs_i  : in  std_logic;
+    asdo_i : in  std_logic_vector(g_port_width-1 downto 0);
+    data_o : out std_logic_vector(g_port_width-1 downto 0));
+end entity;
+
+architecture rtl of altera_spi is
+
+  -- Undocumented Altera ASMI interface components:
+  
+  component cyclone_asmiblock
+    port(
+      dclkin   : in  std_logic;
+      scein    : in  std_logic;
+      sdoin    : in  std_logic;
+      data0out : out std_logic;
+      oe       : in  std_logic);
+  end component;
+
+  component cycloneii_asmiblock 
+    port(
+      dclkin   : in  std_logic;
+      scein    : in  std_logic;
+      sdoin    : in  std_logic;
+      data0out : out std_logic;
+      oe       : in  std_logic);
+  end component;
+
+  component cyclonev_asmiblock 
+    port(
+      dclk     : in std_logic;
+      sce      : in std_logic;
+      oe       : in std_logic;
+      data0out : in std_logic;
+      data1out : in std_logic;
+      data2out : in std_logic;
+      data3out : in std_logic;
+      data0oe  : in std_logic;
+      data1oe  : in std_logic;
+      data2oe  : in std_logic;
+      data3oe  : in std_logic;
+      data0in  : out std_logic;
+      data1in  : out std_logic;
+      data2in  : out std_logic;
+      data3in  : out std_logic);
+  end component;
+  
+  component stratixii_asmiblock 
+    port(
+      dclkin   : in  std_logic;
+      scein    : in  std_logic;
+      sdoin    : in  std_logic;
+      data0out : out std_logic;
+      oe       : in  std_logic);
+  end component;
+  
+  component stratixiii_asmiblock 
+    port(
+      dclkin   : in  std_logic;
+      scein    : in  std_logic;
+      sdoin    : in  std_logic;
+      data0out : out std_logic;
+      oe       : in  std_logic);
+  end component;
+  
+  component stratixiv_asmiblock 
+    port(
+      dclkin   : in  std_logic;
+      scein    : in  std_logic;
+      sdoin    : in  std_logic;
+      data0out : out std_logic;
+      oe       : in  std_logic);
+  end component;
+  
+  component stratixv_asmiblock
+    port(
+      dclk     : in  std_logic;
+      sce      : in  std_logic;
+      oe       : in  std_logic;
+      data0out : in  std_logic;
+      data1out : in  std_logic;
+      data2out : in  std_logic;
+      data3out : in  std_logic;
+      data0oe  : in  std_logic;
+      data1oe  : in  std_logic;
+      data2oe  : in  std_logic;
+      data3oe  : in  std_logic;
+      data0in  : out std_logic;
+      data1in  : out std_logic;
+      data2in  : out std_logic;
+      data3in  : out std_logic);
+  end component;
+  
+  component arriav_asmiblock 
+    port(
+      dclk     : in  std_logic;
+      sce      : in  std_logic;
+      oe       : in  std_logic;
+      data0out : in  std_logic;
+      data1out : in  std_logic;
+      data2out : in  std_logic;
+      data3out : in  std_logic;
+      data0oe  : in  std_logic;
+      data1oe  : in  std_logic;
+      data2oe  : in  std_logic;
+      data3oe  : in  std_logic;
+      data0in  : out std_logic;
+      data1in  : out std_logic;
+      data2in  : out std_logic;
+      data3in  : out std_logic);
+  end component;
+  
+  type t_block is (T_CYCLONE, T_CYCLONEII, T_CYCLONEV,
+                   T_STRATIXII, T_STRATIXIII, T_STRATIXIV, T_STRATIXV, 
+                   T_ARRIAV, 
+                   T_UNKNOWN);
+  
+  function f_block(family : string) return t_block is
+    variable identifier : string(1 to 15) := (others => ' ');
+  begin
+    identifier(family'range) := family;
+    case identifier is
+      when "Cyclone        " => return T_CYCLONE;
+      when "Cyclone II     " => return T_CYCLONEII;
+      when "Cyclone III    " => return T_CYCLONEII;
+      when "Cyclone III LS " => return T_CYCLONEII;
+      when "Cyclone IV E   " => return T_CYCLONEII;
+      when "Cyclone IV GX  " => return T_CYCLONEII;
+      when "Cyclone V      " => return T_CYCLONEV;
+      when "Stratix II     " => return T_STRATIXII;
+      when "Stratix II GX  " => return T_STRATIXII;
+      when "Arria GX       " => return T_STRATIXII;
+      when "Stratix III    " => return T_STRATIXIII;
+      when "Stratix IV     " => return T_STRATIXIV;
+      when "Arria II GX    " => return T_STRATIXIV;
+      when "Arria II GZ    " => return T_STRATIXIV;
+      when "Stratix V      " => return T_STRATIXV;
+      when "Arria V        " => return T_ARRIAV;
+      when others            => return T_UNKNOWN;
+    end case;
+  end f_block;
+  
+  function f_support4(x : t_block) return boolean is
+  begin
+    case x is
+      when T_ARRIAV   => return true;
+      when T_CYCLONEV => return true;
+      when T_STRATIXV => return true;
+      when others     => return false;
+    end case;
+  end f_support4;
+  
+  constant c_block    : t_block := f_block(g_family);
+  constant c_support4 : boolean := f_support4(c_block);
+  
+  signal oe   : std_logic_vector(3 downto 0);
+  signal asdo : std_logic_vector(3 downto 0);
+  signal data : std_logic_vector(3 downto 0);
+  
+  -- attribute altera_attribute : string;
+  -- attribute altera_attribute of rtl: architecture is "SUPPRESS_DA_RULE_INTERNAL=C104";
+
+begin
+
+  assert (c_block /= T_UNKNOWN)
+  report "g_family = " & g_family & " is unsupported"
+  severity error;
+
+  assert (g_port_width = 1 or g_port_width = 4)
+  report "g_port_width must be 1 or 4, not " & integer'image(g_port_width)
+  severity error;
+  
+  assert (g_port_width /= 4 or c_support4)
+  report "g_family = " & g_family & " does not support g_port_width = 4"
+  severity error;
+  
+  data_o <= data(data_o'range);
+  
+  width1 : if g_port_width = 1 generate
+    oe   <= (0 => '0',       others => '1');
+    asdo <= (0 => asdo_i(0), others => '-');
+  end generate;
+  
+  width4 : if g_port_width = 4 generate
+    oe   <= (others => '0');
+    asdo <= asdo_i;
+  end generate;
+  
+  cyclone : if c_block = T_CYCLONE generate
+    cyclone_inst : cyclone_asmiblock 
+      port map(
+        dclkin   => dclk_i,
+        scein    => ncs_i,
+        sdoin    => asdo(0),
+        data0out => data(0),
+        oe       => oe(0));
+  end generate;
+  
+  cycloneii : if c_block = T_CYCLONEII generate
+    cycloneii_inst: cycloneii_asmiblock
+      port map(
+        dclkin   => dclk_i,
+        scein    => ncs_i,
+        sdoin    => asdo(0),
+        data0out => data(0),
+        oe       => oe(0));
+  end generate;
+
+  stratixii : if c_block = T_STRATIXII generate
+    stratixii_inst : stratixii_asmiblock 
+      port map(
+        dclkin   => dclk_i,
+        scein    => ncs_i,
+        sdoin    => asdo(0),
+        data0out => data(0),
+        oe       => oe(0));
+  end generate;
+  
+  stratixiii : if c_block = T_STRATIXIII generate
+    stratixiii_inst: stratixiii_asmiblock 
+      port map(
+	dclkin   => dclk_i,
+	scein    => ncs_i,
+	sdoin    => asdo(0),
+	data0out => data(0),
+	oe       => oe(0));
+  end generate;
+  
+  stratixiv : if c_block = T_STRATIXIV generate
+    asmi_inst: stratixiv_asmiblock
+      port map(
+	dclkin   => dclk_i,
+	scein    => ncs_i,
+	sdoin    => asdo(0),
+	data0out => data(0),
+	oe       => oe(0));
+  end generate;
+  
+  stratixv : if c_block = T_STRATIXV generate
+    stratixv_inst : stratixv_asmiblock
+      port map(
+        dclk     => dclk_i,
+        sce      => ncs_i,
+        oe       => oe(0),
+        data0out => asdo(0),
+        data1out => asdo(1),
+        data2out => asdo(2),
+        data3out => asdo(3),
+        data0oe  => oe(0),
+        data1oe  => oe(1),
+        data2oe  => oe(2),
+        data3oe  => oe(3),
+        data0in  => data(0),
+        data1in  => data(1),
+        data2in  => data(2),
+        data3in  => data(3));
+  end generate;
+  
+  arriav : if c_block = T_ARRIAV generate
+    arriav_inst : arriav_asmiblock
+      port map(
+        dclk     => dclk_i,
+        sce      => ncs_i,
+        oe       => oe(0),
+        data0out => asdo(0),
+        data1out => asdo(1),
+        data2out => asdo(2),
+        data3out => asdo(3),
+        data0oe  => oe(0),
+        data1oe  => oe(1),
+        data2oe  => oe(2),
+        data3oe  => oe(3),
+        data0in  => data(0),
+        data1in  => data(1),
+        data2in  => data(2),
+        data3in  => data(3));
+  end generate;
+  
+  cyclonev : if c_block = T_CYCLONEV generate
+    cyclonev_inst : cyclonev_asmiblock 
+      port map(
+        dclk     => dclk_i,
+        sce      => ncs_i,
+        oe       => oe(0),
+        data0out => asdo(0),
+        data1out => asdo(1),
+        data2out => asdo(2),
+        data3out => asdo(3),
+        data0oe  => oe(0),
+        data1oe  => oe(1),
+        data2oe  => oe(2),
+        data3oe  => oe(3),
+        data0in  => data(0),
+        data1in  => data(1),
+        data2in  => data(2),
+        data3in  => data(3));
+  end generate;
+
+end rtl;
diff --git a/platform/altera/flash/flash_top.vhd b/platform/altera/flash/flash_top.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..8dbc871797e3d9610b6dd0fc01ed8ae0d8358830
--- /dev/null
+++ b/platform/altera/flash/flash_top.vhd
@@ -0,0 +1,76 @@
+library ieee;
+use ieee.std_logic_1164.all;
+
+library work;
+use work.wishbone_pkg.all;
+
+entity flash_top is
+  generic(
+    g_family                 : string;
+    g_port_width             : natural;
+    g_addr_width             : natural;
+    g_input_latch_edge       : std_logic;
+    g_output_latch_edge      : std_logic;
+    g_input_to_output_cycles : natural);
+  port(
+    -- Wishbone interface
+    clk_i   : in  std_logic;
+    rstn_i  : in  std_logic;
+    slave_i : in  t_wishbone_slave_in;
+    slave_o : out t_wishbone_slave_out;
+    -- Clock lines for flash chip
+    clk_out_i : in  std_logic;
+    clk_in_i  : in  std_logic);
+end flash_top;
+
+architecture rtl of flash_top is
+
+  component altera_spi is
+    generic(
+      g_family     : string  := "none";
+      g_port_width : natural := 1);
+    port(
+      dclk_i : in  std_logic;
+      ncs_i  : in  std_logic;
+      asdo_i : in  std_logic_vector(g_port_width-1 downto 0);
+      data_o : out std_logic_vector(g_port_width-1 downto 0));
+  end component;
+  
+  signal flash_ncs  : std_logic;
+  signal flash_asdo : std_logic_vector(g_port_width-1 downto 0);
+  signal flash_data : std_logic_vector(g_port_width-1 downto 0);
+  
+begin
+
+  wb : wb_spi_flash
+    generic map(
+      g_port_width             => g_port_width,
+      g_addr_width             => g_addr_width,
+      g_idle_time              => 3,
+      g_input_latch_edge       => g_input_latch_edge,
+      g_output_latch_edge      => g_output_latch_edge,
+      g_input_to_output_cycles => g_input_to_output_cycles)
+    port map(
+      clk_i              => clk_i,
+      rstn_i             => rstn_i,
+      slave_i            => slave_i,
+      slave_o            => slave_o,
+      clk_out_i          => clk_out_i,
+      clk_in_i           => clk_in_i,
+      ncs_o              => flash_ncs,
+      asdi_o             => flash_asdo,
+      data_i             => flash_data,
+      external_request_i => '0',
+      external_granted_o => open);
+  
+  spi : altera_spi
+    generic map(
+      g_family     => g_family,
+      g_port_width => g_port_width)
+    port map(
+      dclk_i => clk_out_i,
+      ncs_i  => flash_ncs,
+      asdo_i => flash_asdo,
+      data_o => flash_data);
+  
+end rtl;