Here is my setup on a Terasic DE1 board, which uses a CycloneII FPGA and an attached 10ns async SRAM device.
There is a 50MHz (20ns) clock input that is transformed thru a PLL to an 80MHz (12.5ns) clock for the logic.
I can perform memory tests and read and write this SRAM continuously (for days on end...) with no errors occurring.
FYI the SRAM is used as the main memory for an FPGA PDP-8 implementation
# Input 50MHz reference clock
create_clock -period 20.0 -name CLOCK_50 [get_ports {CLOCK_50}]
# Created clocks based on PLLs (CPUCLK = 80MHz)
create_generated_clock -source {pll|altpll_component|pll|inclk[0]} -divide_by 5 -multiply_by 8 -duty_cycle 50 -name CPUCLK {pll|altpll_component|pll|clk[0]}
### external async SRAM timing ###
# address/control outputs
set_output_delay -clock CPUCLK -clock_fall -max 4.0 [get_ports {SRAM_*_L SRAM_A[*]}]
set_output_delay -clock CPUCLK -clock_fall -min 0.5 [get_ports {SRAM_*_L SRAM_A[*]}]
# write data outputs
set_output_delay -clock CPUCLK -max 3.0 [get_ports {SRAM_DQ[*]}]
set_output_delay -clock CPUCLK -min 0.5 [get_ports {SRAM_DQ[*]}]
set_multicycle_path -rise_from CPUCLK -to [get_ports {SRAM_DQ[*]}] -setup 2
set_multicycle_path -rise_from CPUCLK -to [get_ports {SRAM_DQ[*]}] -hold 2
# read data inputs
set_input_delay -clock CPUCLK -max 10.0 [get_ports {SRAM_DQ[*]}]
set_input_delay -clock CPUCLK -min 3.0 [get_ports {SRAM_DQ[*]}]
set_multicycle_path -from [get_ports {SRAM_DQ[*]}] -rise_to CPUCLK -setup 2
set_multicycle_path -from [get_ports {SRAM_DQ[*]}] -rise_to CPUCLK -hold 2
And for reference here is the verilog implementation it references...
module mm8e_memory
#(
// external parameters
parameter TPD = 0, // simulation delay
parameter INTBANKS = 2, // memory size, 4K banks (internal memory)
parameter EXTBANKS = 6 // memory size, 4K banks (external memory)
)
(
// port definitions
input wire clk, // system clock
input wire reset, // system reset
input wire init, // bus init
input wire mr, // memory read
input wire mw, // memory write
input wire [0:2] ema, // extended memory address
input wire [0:11] ma, // memory address
inout wire [0:11] md, // memory data in/out
output reg [14:0] ext_addr, // external memory address
output reg ext_we_l, // external memory write enable
output reg ext_ce_l, // external memory select
output reg ext_oe_l, // external memory read enable
inout wire [11:0] ext_dq // external memory data in/out
);
// internal parameters
localparam
MEMSIZE = 4096*INTBANKS; // internal memory size
// local signals
wire [14:0] addr = {ema[0:2],ma[0:11]}; // full memory address
reg [0:11] memory [0:MEMSIZE-1] /* synthesis ramstyle = "no_rw_check" */;
reg [0:11] mdo;
wire [0:11] mdi = md;
reg mrd;
wire enb_int = (INTBANKS > 0) && (ema <= INTBANKS-1);
wire enb_ext = (EXTBANKS > 0) && (ema >= INTBANKS) && (ema <= INTBANKS+EXTBANKS-1);
// internal memory
initial
$readmemb("meminit.txt", memory, 0, MEMSIZE-1);
always @(posedge clk) mrd <= #TPD mr & enb_int;
wire mwr = mw & enb_int;
always @(posedge clk)
begin
if (mwr) memory[addr] <= #TPD mdi;
mdo <= #TPD memory[addr];
end
assign md = mr & mrd ? mdo : {12{1'bz}};
// external memory
always @(negedge clk)
begin
ext_addr <= #TPD addr;
ext_ce_l <= #TPD 1'b0;
ext_we_l <= #TPD ~( mw & ~mr) | ~ext_we_l;
ext_oe_l <= #TPD ~(~mw & mr);
end
assign ext_dq = mw & ~mr ? mdi : {12{1'bz}};
assign md = mr & ~mw & enb_ext ? ext_dq : {12{1'bz}};
endmodule // mm8e_memory