#!/usr/bin/env python3 # # dramtransfer.py # # DRAM access # # History: # -------- # 21.12.20/KQ Initial test # 30.12.20/KQ Working (renamed) version # 22.04.21/KQ Inbound transfer renamed # 06.05.21/KQ Support for 2 read ports added (for now ...) # from migen import * from migen.fhdl.specials import Memory from litex.soc.interconnect.csr import AutoCSR, CSRStatus, CSRStorage, CSRField, CSRAccess from litex.soc.integration.doc import AutoDoc, ModuleDoc from litex.soc.interconnect.csr import * #from litedram.common import LiteDRAMNativePort from litedram.core.crossbar import LiteDRAMCrossbar from litedram.frontend import dma class DRAM2FPGA(Module, AutoCSR, AutoDoc, ModuleDoc): """ DRAM2FPGA class provides the protocol logic to access DRAM values via LiteDRAM Usage: ###### #. Load ``b32Address`` with base address of range to read from (DRAM: >= 0x40000000) #. Finally, enable processing by setting ``bEnable`` to true (1). #. Once ``bValid`` becomes true (1), FPGA local memory is loaded, deactivate ``bEnable`` #. To retrieve, load ``b9Offset`` with offset (from base adress) to read from (0 .. 511), ``b32Data`` will contain the 32-bit value (from local FPGA memory @offset) Inputs: ####### :b32Address: Base DRAM Address to load from :bEnable: To enable running (after initialization) :b9Offset1: Offset #1 (0..511) into local FPGA memory to read from :b9Offset2: Offset #2 (0..511) into local FPGA memory to read from Output: ####### :bValid: Indicate validity of local FPGA memory, i.e. 'loaded' :b32Data1: Local FPGA memory at b9Offset1 :b32Data2: Local FPGA memory at b9Offset2 """ def __init__(self, maxwords=8, dma_reader=None, sync_fifo=None): # Inputs self.b32Address = CSRStorage(32, reset_less=True, fields=[CSRField("Address", size=32, description="*Field*: 32-Bit value")], description=""" Base DRAM address, to load from """) self.bEnable = CSRStorage(1, reset_less=True, fields=[CSRField("Enable", size=1, description="*Field*: bit", values=[ ("0", "DISABLED", "Loading enabled"), ("1", "ENABLED", "Loading disabled"), ]) ], description=""" Enable/disabling DRAM access """) self.b9Offset1 = CSRStorage(9, reset_less=True, fields=[CSRField("Offset1", size=9, description="*Field*: 9-Bit value (0..511)")], description=""" Offset added to base address, port #1 """) self.b9Offset2 = CSRStorage(9, reset_less=True, fields=[CSRField("Offset2", size=9, description="*Field*: 9-Bit value (0..511)")], description=""" Offset added to base address, port #2 """) # Outputs self.bValid = CSRStorage(1, reset_less=True, fields=[CSRField("Valid", size=1, description="*Field*: bit", values=[ ("0", "INVALID", "Data output not available"), ("1", "VALID", "Data valid"), ]) ], description=""" Data valid indication """) self.b32Data1 = CSRStorage(32, reset_less=True, fields=[CSRField("Data1", size=32, description="*Field*: 32-Bit value")], description=""" Actual value read #1 """) self.b32Data2 = CSRStorage(32, reset_less=True, fields=[CSRField("Data2", size=32, description="*Field*: 32-Bit value")], description=""" Actual value read #2 """) self.b32RCount = CSRStorage(32, reset_less=True, fields=[CSRField("RCount", size=32, description="*Field*: 32-Bit value")], description=""" No. of FIFO entries read so far (only for testing purposes) """) # Local 'wire' data self.b32MemPt = Signal(32) # WRITE: Local FPGA memory offset pointer self.b2Address1 = Signal(3) # READ: Adress conversion helper #1 self.b2Address2 = Signal(3) # READ: Adress conversion helper #2 self.bData1 = Signal(32) # READ: Helper output data #1 self.bData2 = Signal(32) # READ: Helper output data #2 storage = Memory(32, maxwords) # Local FPGA memory self.specials += storage # ---------------------- Local (FPGA) memory DMA filling from DRAM --------------------------------------------- if (dma_reader != None) and (sync_fifo != None): # FPGA local memory write port (driven by FSM, s.b.) wrport = storage.get_port(write_capable=True) self.specials += wrport fsm = FSM(reset_state="IDLE") # FSM starts idling ... self.submodules += fsm # Prepare DRAM address to load from ... self.sync += dma_reader._base.storage.eq(self.b32Address.storage) # Base DRAM adress to load from self.sync += dma_reader._length.storage.eq(maxwords*4) # Fixed byte length of chunk to load from DRAM fsm.act("IDLE", If(self.bEnable.storage & ~self.bValid.storage, # Enabled & not busy already NextValue(dma_reader._localstart, 1), # FPGA DMA self starter (special in litedram/frontend/dma.py) NextValue(self.b32MemPt, 0), # Reset queue offset (counter) NextState("DMAWAIT") ).Elif(~self.bEnable.storage, # Reset from external world first NextValue(self.bValid.storage, 0) # to reset ... before possible re-enable ) ) fsm.act("DMAWAIT", NextValue(dma_reader._localstart, 0), # TODO: May be removed?! Reset FPGA DMA self starter # TODO: Probably wrong?! NextValue(self.bValid.storage, dma_reader._done.status), # Indicate DMA transfer finished to external ... If(dma_reader._done.status, # Wait 'til DMA transfer finishes ... NextState("FIFOREAD") ) ) fsm.act("FIFOREAD", If(dma_reader._localready, # Transfer finished (really?) If(sync_fifo.source.valid, # fifo.readable, # Data in queue waiting? NextValue(self.b32RCount.storage, self.b32RCount.storage + 1), # Increment If(self.b32MemPt < maxwords, # Legal address? NextValue(wrport.adr, self.b32MemPt), # Local offset into memory NextValue(wrport.dat_w, sync_fifo.source.payload.data), # Store current value -> memory NextValue(wrport.we, 1) # Write enable ), NextValue(sync_fifo.source.ready, 1), # fifo.re, 1), # ACK readable, request next FIFO entry NextState("TESTFIFO") ) ) ) fsm.act("TESTFIFO", NextValue(wrport.we, 0), # Stop transfer to memory If(self.b32MemPt >= (maxwords-1), NextValue(self.b32MemPt, 0), # Reset counter NextValue(self.bValid.storage, 1), # Indicate validity of results NextState("IDLE") ).Else( NextValue(self.b32MemPt, self.b32MemPt + 1), # Increment NextState("FIFOREAD") ), NextValue(sync_fifo.source.ready, 0) #fifo.re, 0), # Reset ACK ) # --------------------------- Local (FPGA) memory retrieval access ----------------------------------------------- # FPGA local memory read port rdport1 = storage.get_port() self.specials += rdport1 rdport2 = storage.get_port() self.specials += rdport2 self.comb += [ # Read from (FPGA local) memory self.b2Address1.eq(self.b9Offset1.storage[0:2]), # Filter bits 0..1 (range 0-3) If(self.b9Offset1.storage < maxwords, #rdport.adr.eq(self.b9Offset1.storage), # w/ translation! If(self.b2Address1 == 0, rdport1.adr.eq(self.b9Offset1.storage | 3) # 0->3 ).Elif(self.b2Address1 == 1, rdport1.adr.eq((self.b9Offset1.storage & 0x1FC) | 2) # 1->2 ).Elif(self.b2Address1 == 2, rdport1.adr.eq((self.b9Offset1.storage & 0x1FC) | 1) # 2->1 ).Elif(self.b2Address1 == 3, rdport1.adr.eq(self.b9Offset1.storage & 0x1FC) # 3->0 ), self.bData1.eq(rdport1.dat_r) # Assign to external var. ... ), self.b2Address2.eq(self.b9Offset2.storage[0:2]), # Filter bits 0..1 (range 0-3) If(self.b9Offset2.storage < maxwords, #rdport.adr.eq(self.b9Offset2.storage), # w/ translation! If(self.b2Address2 == 0, rdport2.adr.eq(self.b9Offset2.storage | 3) # 0->3 ).Elif(self.b2Address2 == 1, rdport2.adr.eq((self.b9Offset2.storage & 0x1FC) | 2) # 1->2 ).Elif(self.b2Address2 == 2, rdport2.adr.eq((self.b9Offset2.storage & 0x1FC) | 1) # 2->1 ).Elif(self.b2Address2 == 3, rdport2.adr.eq(self.b9Offset2.storage & 0x1FC) # 3->0 ), self.bData2.eq(rdport2.dat_r) # Assign to external var. ... ), ] self.sync += self.b32Data1.storage.eq(self.bData1) # Assign to external var. ... self.sync += self.b32Data2.storage.eq(self.bData2) # Assign to external var. ... class FPGA2DRAM(Module, AutoCSR, AutoDoc, ModuleDoc): """ FPGA2DRAM class provides the protocol logic to write single DRAM values via LiteDRAM Usage: ###### #. Make sure ``bEnable`` is reset (0) #. ``bData`` must contain the actual value to store to DRAM #. Load ``b32Address`` with base address to write to (DRAM: >= 0x40000000) #. Finally, enable processing by setting ``bEnable`` to true (1). #. Once ``bValid`` becomes true (1), FPGA local memory will be written, deactivate ``bEnable`` Inputs: ####### :bData: Data (32-bit) to store to DRAM :b32Address: Base DRAM Address to write to :bEnable: To enable running (after initialization) Output: ####### :bValid: Indicate validity of DRAM Memory, i.e. 'written' """ def __init__(self, dma_writer=None, sync_fifo=None): # Inputs self.b32Address = CSRStorage(32, reset_less=True, fields=[CSRField("Address", size=32, description="*Field*: 32-Bit value")], description=""" Base DRAM address, to write to """) self.bEnable = CSRStorage(1, reset_less=True, fields=[CSRField("Enable", size=1, description="*Field*: bit", values=[ ("0", "DISABLED", "Writing enabled"), ("1", "ENABLED", "Writing disabled"), ]) ], description=""" Enable/disabling DRAM access """) # Outputs self.bValid = CSRStorage(1, reset_less=True, fields=[CSRField("Valid", size=1, description="*Field*: bit", values=[ ("0", "INVALID", "Data output not available"), ("1", "VALID", "Data valid"), ]) ], description=""" DRAM data valid indication """) #self.b32Data = CSRStorage(32, reset_less=True, # fields=[CSRField("Data", size=32, description="*Field*: 32-Bit value")], # description=""" # Actual value to be written # """) # Local wiring: Address mixer self.bData = Signal(32) # Actual value to store (not on wishbone-bus!) self.bAddress = Signal(32) # Calculated actual address to write to ... self.b4Address = Signal(4) # WRITE: Adress conversion helper self.comb += [ # Calculate adjusted address self.b4Address.eq(self.b32Address.storage[0:4]), # Filter bits 0..3 (range 0-F, 4-byte steps) If(self.b4Address == 0, self.bAddress.eq(self.b32Address.storage | 0xC) # 0->12 ).Elif(self.b4Address == 4, self.bAddress.eq((self.b32Address.storage & 0xFFFFFFF0) | 8) # 4->8 ).Elif(self.b4Address == 8, self.bAddress.eq((self.b32Address.storage & 0xFFFFFFF0) | 4) # 8->4 ).Elif(self.b4Address == 12, self.bAddress.eq(self.b32Address.storage & 0xFFFFFFF0) # 12->0 ), ] # Prepare DRAM address to write to ... self.sync += dma_writer._base.storage.eq(self.bAddress) # Base DRAM adress to write to self.sync += dma_writer._length.storage.eq(4) # Fixed byte length of chunk to store to DRAM (1 word=4 bytes) fsm = FSM(reset_state="IDLE") # FSM starts idling ... self.submodules += fsm fsm.act("IDLE", # Store data in FIFO If(self.bEnable.storage & ~self.bValid.storage, # Enabled & not busy already If(sync_fifo.sink.ready, # fifo.writable! NextValue(sync_fifo.sink.payload.data, self.bData), # Actual 32-bit word to store NextValue(sync_fifo.sink.valid, 1), # fifo.we! ACK writable, request next FIFO entry NextState("FIFOWRITEPULSE") ) ).Elif(~self.bEnable.storage, # Reset from external world first NextValue(self.bValid.storage, 0) # to reset ... before possible re-enable ) ) fsm.act("FIFOWRITEPULSE", # Reset FIFO write pulse NextValue(sync_fifo.sink.valid, 0), # End FIFO write pulse (value now in FIFO) NextState("STARTDMA") ) fsm.act("STARTDMA", NextValue(dma_writer._localstart, 1), # FPGA DMA self starter (special in litedram/frontend/dma.py) NextState("DMAWAIT") ) fsm.act("DMAWAIT", NextValue(dma_writer._localstart, 0), # Reset FPGA DMA self starter NextValue(self.bValid.storage, dma_writer._done.status), # Indicate DMA transfer finished to external ... If(dma_writer._done.status, # Wait 'til DMA transfer finishes ... NextState("WAITCYCLES"), ) ) fsm.act("WAITCYCLES", NextState("IDLE"), ) if __name__ == "__main__": print("*** This is a module only! ***")