248 lines
9.8 KiB
Plaintext
248 lines
9.8 KiB
Plaintext
package ECP5_RAM;
|
|
|
|
import Printf::*;
|
|
|
|
// ECP5_EBRWriteMode specifies what the EBR outputs on a write cycle.
|
|
typedef enum {
|
|
// In Normal mode, the EBR's output on a write cycle is undefined.
|
|
Normal,
|
|
// In WriteThrough mode, the EBR outputs the new value at the
|
|
// written address.
|
|
WriteThrough,
|
|
// In ReadBeforeWrite mode, the EBR outputs the prior value of the
|
|
// written address. ReadBeforeWrite is only available on 9 and 18
|
|
// bit ports.
|
|
ReadBeforeWrite
|
|
} ECP5_EBRWriteMode deriving (Bits, Eq);
|
|
|
|
// ECP5_EBRPortConfig is the static configuration of an EBR port.
|
|
typedef struct {
|
|
Clock clk;
|
|
Reset rstN;
|
|
// By default, ECP5 EBRs only register the input address and write
|
|
// data, giving a 1-cycle latency for operations. If
|
|
// registered_output is true, the output value is also registered,
|
|
// resulting in 2 cycles of latency but shorter datapaths.
|
|
Bool registered_output;
|
|
// chip_select_addr is the chip address of this EBR port. put
|
|
// method invocations whose select argument don't match this
|
|
// address are ignored.
|
|
UInt#(3) chip_select_addr;
|
|
// write_mode specifies the output's behavior for write operations.
|
|
ECP5_EBRWriteMode write_mode;
|
|
} ECP5_EBRPortConfig;
|
|
|
|
instance DefaultValue#(ECP5_EBRPortConfig);
|
|
defaultValue = ECP5_EBRPortConfig{
|
|
clk: noClock,
|
|
rstN: noReset,
|
|
registered_output: False,
|
|
chip_select_addr: 0,
|
|
write_mode: Normal
|
|
};
|
|
endinstance
|
|
|
|
(* always_ready *)
|
|
interface ECP5_EBRCoreInnerPort;
|
|
// Put starts a read or write operation, if select's value matches
|
|
// the port's configured chip_select_addr.
|
|
method Action put(UInt#(3) select, Bool write, Bit#(14) address, Bit#(18) data);
|
|
// Read returns the value on the EBR's output port. The output
|
|
// value is only defined when the read follows a put with the
|
|
// correct number of latency cycles for the port's configuration.
|
|
method Bit#(18) read();
|
|
endinterface
|
|
|
|
interface ECP5_EBRCoreInner;
|
|
interface ECP5_EBRCoreInnerPort portA;
|
|
interface ECP5_EBRCoreInnerPort portB;
|
|
endinterface
|
|
|
|
// mkECP5_EBRCoreInner instantiates an ECP5 EBR primitive with the
|
|
// given configuration. The returned interface has full-width I/O
|
|
// ports
|
|
import "BVI" ECP5_RAM =
|
|
module mkECP5_EBRCoreInner#(ECP5_EBRPortConfig port_a,
|
|
ECP5_EBRPortConfig port_b,
|
|
Integer portA_width,
|
|
Integer portB_width)
|
|
(ECP5_EBRCoreInner);
|
|
|
|
default_clock no_clock;
|
|
default_reset no_reset;
|
|
|
|
input_clock portA_clk(CLKA, (* unused *)CLKA_GATE) = port_a.clk;
|
|
input_reset portA_rstN(RSTA) clocked_by(portA_clk) = port_a.rstN;
|
|
|
|
input_clock portB_clk(CLKB, (* unused *)CLKB_GATE) = port_b.clk;
|
|
input_reset portB_rstN(RSTB) clocked_by(portB_clk) = port_b.rstN;
|
|
|
|
parameter DATA_WIDTH_A = portA_width;
|
|
parameter REGMODE_A = port_a.registered_output ? "OUTREG" : "NOREG";
|
|
parameter CSDECODE_A = "0b000"; //$format("0b%b", port_a.chip_select_addr);
|
|
parameter WRITEMODE_A = case (port_a.write_mode) matches
|
|
Normal: "NORMAL";
|
|
WriteThrough: "WRITETHROUGH";
|
|
ReadBeforeWrite: "READBEFOREWRITE";
|
|
endcase;
|
|
|
|
parameter DATA_WIDTH_B = portB_width;
|
|
parameter REGMODE_B = port_b.registered_output ? "OUTREG" : "NOREG";
|
|
parameter CSDECODE_B = "0b000"; //$format("0b%b", port_b.chip_select_addr);
|
|
parameter WRITEMODE_B = case (port_b.write_mode) matches
|
|
Normal: "NORMAL";
|
|
WriteThrough: "WRITETHROUGH";
|
|
ReadBeforeWrite: "READBEFOREWRITE";
|
|
endcase;
|
|
|
|
port OCEA = True;
|
|
port OCEB = True;
|
|
|
|
interface ECP5_EBRCoreInnerPort portA;
|
|
method put((*reg*)CSA, (*reg*)WEA, (*reg*)ADA, (*reg*)DIA) enable(CEA) clocked_by(portA_clk) reset_by(portA_rstN);
|
|
method DOA read() clocked_by(portA_clk) reset_by(portA_rstN);
|
|
endinterface
|
|
interface ECP5_EBRCoreInnerPort portB;
|
|
method put((*reg*)CSB, (*reg*)WEB, (*reg*)ADB, (*reg*)DIB) enable(CEB) clocked_by(portB_clk) reset_by(portB_rstN);
|
|
method DOB read() clocked_by(portB_clk) reset_by(portB_rstN);
|
|
endinterface
|
|
|
|
schedule (portA.read) CF (portA.read, portA.put);
|
|
schedule (portA.put) C (portA.put);
|
|
schedule (portB.read) CF (portB.read, portB.put);
|
|
schedule (portB.put) C (portB.put);
|
|
endmodule : mkECP5_EBRCoreInner
|
|
|
|
module checkSizes#(addr a, data d, String module_name, String port_name)(Empty)
|
|
provisos (Bits#(addr, addr_sz),
|
|
Bits#(data, data_sz));
|
|
|
|
let data_sz = valueOf(data_sz);
|
|
let addr_sz = valueOf(addr_sz);
|
|
let addr_max = case (data_sz) matches
|
|
1: 14;
|
|
2: 13;
|
|
4: 12;
|
|
9: 11;
|
|
18: 10;
|
|
default: error(sprintf("invalid data width %d for port, must be one of 1,2,4,9,18", data_sz));
|
|
endcase;
|
|
if (addr_sz > addr_max) begin
|
|
addr dummy = ?;
|
|
errorM(sprintf("The address type for port %s of %s is wider than the hardware can implement. "+
|
|
"Address type %s has %d bits, maximum is %d",
|
|
port_name, module_name,
|
|
printType(typeOf(dummy)),
|
|
addr_sz,
|
|
addr_max));
|
|
end
|
|
endmodule
|
|
|
|
// ECP5_EBRCorePort is the raw interface to one port of an ECP5 EBR
|
|
// memory block.
|
|
//
|
|
// The port has no implicit conditions, it is the caller's
|
|
// responsibility to wait the correct number of cycles after a put()
|
|
// before capturing data with read(). The caller must wait 1 cycle for
|
|
// unregistered ports, and 2 cycles for registered ports. When invoked
|
|
// at other times, read() returns an unspecified arbitrary value.
|
|
interface ECP5_EBRCorePort#(type addr, type data);
|
|
method Action put(UInt#(3) chip_select, Bool write, addr address, data datain);
|
|
method data read();
|
|
endinterface
|
|
|
|
// ECP5_EBRCore is the raw interface to an ECP5 EBR memory block.
|
|
//
|
|
// The ports have no implicit conditions, the caller must wait the
|
|
// correct number of latency cycles to get valid data.
|
|
//
|
|
// It is the caller's responsibility to enforce synchronization
|
|
// between the ports, as specified in Lattice Technical Note 02204:
|
|
// the two ports must not issue concurrent writes to the same address,
|
|
// or a write concurrent with a read of the same address. If the two
|
|
// ports are being operated from different clock domains, the caller
|
|
// must implement appropriate synchronization to ensure that no
|
|
// read-during-write or write-during-write races occur.
|
|
interface ECP5_EBRCore#(type portA_addr, type portA_data, type portB_addr, type portB_data);
|
|
interface ECP5_EBRCorePort#(portA_addr, portA_data) portA;
|
|
interface ECP5_EBRCorePort#(portB_addr, portB_data) portB;
|
|
endinterface
|
|
|
|
// mkECP5_EBRCore instantiates an ECP5 EBR memory primitive with the
|
|
// given configuration. This memory has no implicit or explicit
|
|
// conditions, the caller is responsible for upholding the primitive's
|
|
// timing and synchronization requirements.
|
|
module mkECP5_EBRCore#(ECP5_EBRPortConfig port_a,
|
|
ECP5_EBRPortConfig port_b)
|
|
(ECP5_EBRCore#(addr_a, data_a, addr_b, data_b))
|
|
provisos (Bits#(addr_a, addr_sz_a),
|
|
Bits#(data_a, data_sz_a),
|
|
Bits#(addr_b, addr_sz_b),
|
|
Bits#(data_b, data_sz_b),
|
|
Add#(addr_a_pad, addr_sz_a, 14),
|
|
Add#(data_a_pad, data_sz_a, 18),
|
|
Add#(addr_b_pad, addr_sz_b, 14),
|
|
Add#(data_b_pad, data_sz_b, 18));
|
|
|
|
checkSizes(addr_a ' (?), data_a ' (?), "mkECP5_EBRCore", "A");
|
|
checkSizes(addr_b ' (?), data_b ' (?), "mkECP5_EBRCore", "B");
|
|
|
|
let inner <- mkECP5_EBRCoreInner(port_a, port_b, valueOf(data_sz_a), valueOf(data_sz_b));
|
|
|
|
interface ECP5_EBRCorePort portA;
|
|
method Action put(UInt#(3) chip_select, Bool write, addr_a address, data_a datain);
|
|
inner.portA.put(chip_select, write, zeroExtend(pack(address)), zeroExtend(pack(datain)));
|
|
endmethod
|
|
method data_a read();
|
|
return unpack(truncate(inner.portA.read()));
|
|
endmethod
|
|
endinterface
|
|
|
|
interface ECP5_EBRCorePort portB;
|
|
method Action put(UInt#(3) chip_select, Bool write, addr_b address, data_b datain);
|
|
inner.portB.put(chip_select, write, zeroExtend(pack(address)), zeroExtend(pack(datain)));
|
|
endmethod
|
|
method data_b read();
|
|
return unpack(truncate(inner.portB.read()));
|
|
endmethod
|
|
endinterface
|
|
endmodule
|
|
|
|
module mkECP5_EBRCoreByte#(ECP5_EBRPortConfig port_a,
|
|
ECP5_EBRPortConfig port_b)
|
|
(ECP5_EBRCore#(addr_a, data_a, addr_b, data_b))
|
|
provisos (Bits#(addr_a, 12),
|
|
Bits#(data_a, 8),
|
|
Bits#(addr_b, 12),
|
|
Bits#(data_b, 8));
|
|
|
|
let ebr1 <- mkECP5_EBRCore(port_a, port_b);
|
|
let ebr2 <- mkECP5_EBRCore(port_a, port_b);
|
|
|
|
interface ECP5_EBRCorePort portA;
|
|
method Action put(UInt#(3) chip_select, Bool write, addr_a address, data_a datain);
|
|
let data_bits = pack(datain);
|
|
ebr1.portA.put(chip_select, write, address, data_bits[7:4]);
|
|
ebr2.portA.put(chip_select, write, address, data_bits[3:0]);
|
|
endmethod
|
|
|
|
method data_a read();
|
|
return unpack({ebr1.portA.read(), ebr2.portA.read});
|
|
endmethod
|
|
endinterface
|
|
|
|
interface ECP5_EBRCorePort portB;
|
|
method Action put(UInt#(3) chip_select, Bool write, addr_b address, data_b datain);
|
|
let data_bits = pack(datain);
|
|
ebr1.portB.put(chip_select, write, address, data_bits[7:4]);
|
|
ebr2.portB.put(chip_select, write, address, data_bits[3:0]);
|
|
endmethod
|
|
|
|
method data_b read();
|
|
return unpack({ebr1.portB.read(), ebr2.portB.read});
|
|
endmethod
|
|
endinterface
|
|
endmodule
|
|
|
|
endpackage
|