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