gary/lib/ECP5_RAM.bsv

272 lines
11 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 {
// clk, if specified, is the Clock to use for the port. If
// unspecified, uses the module default clock.
Maybe#(Clock) clk;
// rstN, if specified, is the Reset to use for the port. If
// unspecified, uses the module default reset.
Maybe#(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: defaultValue,
rstN: defaultValue,
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);
let defClk <- exposeCurrentClock;
let defRstN <- exposeCurrentReset;
let portA_bsv_clock = case (port_a.clk) matches
tagged Invalid: defClk;
tagged Valid .clk: clk;
endcase;
let portA_bsv_rstN = case (port_a.rstN) matches
tagged Invalid: defRstN;
tagged Valid .rstN: rstN;
endcase;
let portB_bsv_clock = case (port_b.clk) matches
tagged Invalid: defClk;
tagged Valid .clk: clk;
endcase;
let portB_bsv_rstN = case (port_b.rstN) matches
tagged Invalid: defRstN;
tagged Valid .rstN: rstN;
endcase;
default_clock no_clock;
default_reset no_reset;
input_clock portA_clk(CLKA, (* unused *)CLKA_GATE) = portA_bsv_clock;
input_reset portA_rstN(RSTA) clocked_by(portA_clk) = portA_bsv_rstN;
input_clock portB_clk(CLKB, (* unused *)CLKB_GATE) = portB_bsv_clock;
input_reset portB_rstN(RSTB) clocked_by(portB_clk) = portB_bsv_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