1 # This file is Copyright (c) 2015 Sebastien Bourdeauducq <sb@m-labs.hk>
2 # This file is Copyright (c) 2016-2019 Florent Kermarrec <florent@enjoy-digital.fr>
3 # This file is Copyright (c) 2020 LambdaConcept <contact@lambdaconcept.com>
6 """LiteDRAM BankMachine (Rows/Columns management)."""
12 from gram
.common
import *
13 from gram
.core
.multiplexer
import *
14 from gram
.compat
import delayed_enter
15 import gram
.stream
as stream
17 __ALL__
= ["BankMachine"]
19 # AddressSlicer ------------------------------------------------------------------------------------
23 """Helper for extracting row/col from address
25 Column occupies lower bits of the address, row - higher bits. Address has
26 a forced alignment, so column does not contain alignment bits.
29 def __init__(self
, colbits
, address_align
):
30 self
.colbits
= colbits
31 self
.address_align
= address_align
33 def row(self
, address
):
34 split
= self
.colbits
- self
.address_align
35 return address
[split
:]
37 def col(self
, address
):
38 split
= self
.colbits
- self
.address_align
39 return Cat(Repl(0, self
.address_align
), address
[:split
])
41 # BankMachine --------------------------------------------------------------------------------------
44 class BankMachine(Elaboratable
):
45 """Converts requests from ports into DRAM commands
47 BankMachine abstracts single DRAM bank by keeping track of the currently
48 selected row. It converts requests from LiteDRAMCrossbar to targetted
49 to that bank into DRAM commands that go to the Multiplexer, inserting any
50 needed activate/precharge commands (with optional auto-precharge). It also
51 keeps track and enforces some DRAM timings (other timings are enforced in
54 BankMachines work independently from the data path (which connects
55 LiteDRAMCrossbar with the Multiplexer directly).
57 Stream of requests from LiteDRAMCrossbar is being queued, so that reqeust
58 can be "looked ahead", and auto-precharge can be performed (if enabled in
61 Lock (cmd_layout.lock) is used to synchronise with LiteDRAMCrossbar. It is
63 - there is a valid command awaiting in `cmd_buffer_lookahead` - this buffer
64 becomes ready simply when the next data gets fetched to the `cmd_buffer`
65 - there is a valid command in `cmd_buffer` - `cmd_buffer` becomes ready
66 when the BankMachine sends wdata_ready/rdata_valid back to the crossbar
73 LiteDRAMInterface address width
75 Address alignment depending on burst length
77 Number of separate DRAM chips (width of chip select)
78 settings : ControllerSettings
79 LiteDRAMController settings
83 req : Record(cmd_layout)
84 Stream of requests from LiteDRAMCrossbar
85 refresh_req : Signal(), in
86 Indicates that refresh needs to be done, connects to Refresher.cmd.valid
87 refresh_gnt : Signal(), out
88 Indicates that refresh permission has been granted, satisfying timings
89 cmd : Endpoint(cmd_request_rw_layout)
90 Stream of commands to the Multiplexer
93 def __init__(self
, n
, address_width
, address_align
, nranks
, settings
):
94 self
.settings
= settings
95 self
.req
= req
= Record(cmd_layout(address_width
))
96 self
.refresh_req
= refresh_req
= Signal()
97 self
.refresh_gnt
= refresh_gnt
= Signal()
99 a
= settings
.geom
.addressbits
100 ba
= settings
.geom
.bankbits
+ log2_int(nranks
)
101 self
.cmd
= stream
.Endpoint(cmd_request_rw_layout(a
, ba
))
103 self
._address
_align
= address_align
106 def elaborate(self
, platform
):
109 auto_precharge
= Signal()
111 # Command buffer ---------------------------------------------------------------------------
112 cmd_buffer_layout
= [("we", 1), ("addr", len(self
.req
.addr
))]
113 cmd_buffer_lookahead
= stream
.SyncFIFO(
114 cmd_buffer_layout
, self
.settings
.cmd_buffer_depth
,
115 buffered
=self
.settings
.cmd_buffer_buffered
)
116 # 1 depth buffer to detect row change
117 cmd_buffer
= stream
.Buffer(cmd_buffer_layout
)
118 m
.submodules
+= cmd_buffer_lookahead
, cmd_buffer
120 cmd_buffer_lookahead
.sink
.valid
.eq(self
.req
.valid
),
121 self
.req
.ready
.eq(cmd_buffer_lookahead
.sink
.ready
),
122 cmd_buffer_lookahead
.sink
.payload
.we
.eq(self
.req
.we
),
123 cmd_buffer_lookahead
.sink
.payload
.addr
.eq(self
.req
.addr
),
125 cmd_buffer_lookahead
.source
.connect(cmd_buffer
.sink
),
126 cmd_buffer
.source
.ready
.eq(
127 self
.req
.wdata_ready | self
.req
.rdata_valid
),
128 self
.req
.lock
.eq(cmd_buffer_lookahead
.source
.valid |
129 cmd_buffer
.source
.valid
),
132 slicer
= _AddressSlicer(
133 self
.settings
.geom
.colbits
, self
._address
_align
)
135 # Row tracking -----------------------------------------------------------------------------
136 row
= Signal(self
.settings
.geom
.rowbits
)
137 row_opened
= Signal()
141 m
.d
.comb
+= row_hit
.eq(row
== slicer
.row(cmd_buffer
.source
.addr
))
142 with m
.If(row_close
):
143 m
.d
.sync
+= row_opened
.eq(0)
144 with m
.Elif(row_open
):
147 row
.eq(slicer
.row(cmd_buffer
.source
.addr
)),
150 # Address generation -----------------------------------------------------------------------
151 row_col_n_addr_sel
= Signal()
152 m
.d
.comb
+= self
.cmd
.ba
.eq(self
._n
)
153 with m
.If(row_col_n_addr_sel
):
154 m
.d
.comb
+= self
.cmd
.a
.eq(slicer
.row(cmd_buffer
.source
.addr
))
156 m
.d
.comb
+= self
.cmd
.a
.eq((auto_precharge
<< 10)
157 | slicer
.col(cmd_buffer
.source
.addr
))
159 # tWTP (write-to-precharge) controller -----------------------------------------------------
160 write_latency
= math
.ceil(
161 self
.settings
.phy
.cwl
/ self
.settings
.phy
.nphases
)
162 precharge_time
= write_latency
+ self
.settings
.timing
.tWR
+ \
163 self
.settings
.timing
.tCCD
# AL=0
164 m
.submodules
.twtpcon
= twtpcon
= tXXDController(precharge_time
)
165 m
.d
.comb
+= twtpcon
.valid
.eq(self
.cmd
.valid
&
166 self
.cmd
.ready
& self
.cmd
.is_write
)
168 # tRC (activate-activate) controller -------------------------------------------------------
169 m
.submodules
.trccon
= trccon
= tXXDController(self
.settings
.timing
.tRC
)
170 m
.d
.comb
+= trccon
.valid
.eq(self
.cmd
.valid
& self
.cmd
.ready
& row_open
)
172 # tRAS (activate-precharge) controller -----------------------------------------------------
173 m
.submodules
.trascon
= trascon
= tXXDController(self
.settings
.timing
.tRAS
)
174 m
.d
.comb
+= trascon
.valid
.eq(self
.cmd
.valid
& self
.cmd
.ready
& row_open
)
176 # Auto Precharge generation ----------------------------------------------------------------
177 # generate auto precharge when current and next cmds are to different rows
178 if self
.settings
.with_auto_precharge
:
179 with m
.If(cmd_buffer_lookahead
.source
.valid
& cmd_buffer
.source
.valid
):
180 with m
.If(slicer
.row(cmd_buffer_lookahead
.source
.addr
) != slicer
.row(cmd_buffer
.source
.addr
)):
181 m
.d
.comb
+= auto_precharge
.eq(row_close
== 0)
183 # Control and command generation FSM -------------------------------------------------------
184 # Note: tRRD, tFAW, tCCD, tWTR timings are enforced by the multiplexer
186 with m
.State("Regular"):
187 with m
.If(self
.refresh_req
):
189 with m
.Elif(cmd_buffer
.source
.valid
):
190 with m
.If(row_opened
):
193 self
.cmd
.valid
.eq(1),
196 with m
.If(cmd_buffer
.source
.we
):
198 self
.req
.wdata_ready
.eq(self
.cmd
.ready
),
199 self
.cmd
.is_write
.eq(1),
204 self
.req
.rdata_valid
.eq(self
.cmd
.ready
),
205 self
.cmd
.is_read
.eq(1),
207 with m
.If(self
.cmd
.ready
& auto_precharge
):
208 m
.next
= "Autoprecharge"
214 with m
.State("Precharge"):
215 m
.d
.comb
+= row_close
.eq(1)
217 with m
.If(twtpcon
.ready
& trascon
.ready
):
219 self
.cmd
.valid
.eq(1),
222 self
.cmd
.is_cmd
.eq(1),
225 with m
.If(self
.cmd
.ready
):
228 with m
.State("Autoprecharge"):
229 m
.d
.comb
+= row_close
.eq(1)
231 with m
.If(twtpcon
.ready
& trascon
.ready
):
234 with m
.State("Activate"):
235 with m
.If(trccon
.ready
):
237 row_col_n_addr_sel
.eq(1),
239 self
.cmd
.valid
.eq(1),
240 self
.cmd
.is_cmd
.eq(1),
243 with m
.If(self
.cmd
.ready
):
246 with m
.State("Refresh"):
249 self
.cmd
.is_cmd
.eq(1),
252 with m
.If(twtpcon
.ready
):
253 m
.d
.comb
+= self
.refresh_gnt
.eq(1)
254 with m
.If(~self
.refresh_req
):
257 delayed_enter(m
, "tRP", "Activate", self
.settings
.timing
.tRP
- 1)
258 delayed_enter(m
, "tRCD", "Regular", self
.settings
.timing
.tRCD
- 1)