Add more memory tests
[gram.git] / gram / core / bankmachine.py
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>
4 # License: BSD
5
6 """LiteDRAM BankMachine (Rows/Columns management)."""
7
8 import math
9
10 from nmigen import *
11
12 from gram.common import *
13 from gram.core.multiplexer import *
14 from gram.compat import delayed_enter
15 import gram.stream as stream
16
17 __ALL__ = ["BankMachine"]
18
19 # AddressSlicer ------------------------------------------------------------------------------------
20
21
22 class _AddressSlicer:
23 """Helper for extracting row/col from address
24
25 Column occupies lower bits of the address, row - higher bits. Address has
26 a forced alignment, so column does not contain alignment bits.
27 """
28
29 def __init__(self, colbits, address_align):
30 self.colbits = colbits
31 self.address_align = address_align
32
33 def row(self, address):
34 split = self.colbits - self.address_align
35 return address[split:]
36
37 def col(self, address):
38 split = self.colbits - self.address_align
39 return Cat(Repl(0, self.address_align), address[:split])
40
41 # BankMachine --------------------------------------------------------------------------------------
42
43
44 class BankMachine(Elaboratable):
45 """Converts requests from ports into DRAM commands
46
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
52 the Multiplexer).
53
54 BankMachines work independently from the data path (which connects
55 LiteDRAMCrossbar with the Multiplexer directly).
56
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
59 settings).
60
61 Lock (cmd_layout.lock) is used to synchronise with LiteDRAMCrossbar. It is
62 being held when:
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
67
68 Parameters
69 ----------
70 n : int
71 Bank number
72 address_width : int
73 LiteDRAMInterface address width
74 address_align : int
75 Address alignment depending on burst length
76 nranks : int
77 Number of separate DRAM chips (width of chip select)
78 settings : ControllerSettings
79 LiteDRAMController settings
80
81 Attributes
82 ----------
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
91 """
92
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()
98
99 a = settings.geom.addressbits
100 ba = settings.geom.bankbits + log2_int(nranks)
101 self.cmd = stream.Endpoint(cmd_request_rw_layout(a, ba))
102
103 self._address_align = address_align
104 self._n = n
105
106 def elaborate(self, platform):
107 m = Module()
108
109 auto_precharge = Signal()
110
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
119 m.d.comb += [
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),
124
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),
130 ]
131
132 slicer = _AddressSlicer(self.settings.geom.colbits, self._address_align)
133
134 # Row tracking -----------------------------------------------------------------------------
135 row = Signal(self.settings.geom.rowbits)
136 row_opened = Signal()
137 row_hit = Signal()
138 row_open = Signal()
139 row_close = Signal()
140 m.d.comb += row_hit.eq(row == slicer.row(cmd_buffer.source.addr))
141 with m.If(row_close):
142 m.d.sync += row_opened.eq(0)
143 with m.Elif(row_open):
144 m.d.sync += [
145 row_opened.eq(1),
146 row.eq(slicer.row(cmd_buffer.source.addr)),
147 ]
148
149 # Address generation -----------------------------------------------------------------------
150 row_col_n_addr_sel = Signal()
151 m.d.comb += self.cmd.ba.eq(self._n)
152 with m.If(row_col_n_addr_sel):
153 m.d.comb += self.cmd.a.eq(slicer.row(cmd_buffer.source.addr))
154 with m.Else():
155 m.d.comb += self.cmd.a.eq((auto_precharge << 10)
156 | slicer.col(cmd_buffer.source.addr))
157
158 # tWTP (write-to-precharge) controller -----------------------------------------------------
159 write_latency = math.ceil(
160 self.settings.phy.cwl / self.settings.phy.nphases)
161 precharge_time = write_latency + self.settings.timing.tWR + \
162 self.settings.timing.tCCD # AL=0
163 m.submodules.twtpcon = twtpcon = tXXDController(precharge_time)
164 m.d.comb += twtpcon.valid.eq(self.cmd.valid &
165 self.cmd.ready & self.cmd.is_write)
166
167 # tRC (activate-activate) controller -------------------------------------------------------
168 m.submodules.trccon = trccon = tXXDController(self.settings.timing.tRC)
169 m.d.comb += trccon.valid.eq(self.cmd.valid & self.cmd.ready & row_open)
170
171 # tRAS (activate-precharge) controller -----------------------------------------------------
172 m.submodules.trascon = trascon = tXXDController(self.settings.timing.tRAS)
173 m.d.comb += trascon.valid.eq(self.cmd.valid & self.cmd.ready & row_open)
174
175 # Auto Precharge generation ----------------------------------------------------------------
176 # generate auto precharge when current and next cmds are to different rows
177 if self.settings.with_auto_precharge:
178 with m.If(cmd_buffer_lookahead.source.valid & cmd_buffer.source.valid):
179 with m.If(slicer.row(cmd_buffer_lookahead.source.addr) != slicer.row(cmd_buffer.source.addr)):
180 m.d.comb += auto_precharge.eq(row_close == 0)
181
182 # Control and command generation FSM -------------------------------------------------------
183 # Note: tRRD, tFAW, tCCD, tWTR timings are enforced by the multiplexer
184 with m.FSM():
185 with m.State("Regular"):
186 with m.If(self.refresh_req):
187 m.next = "Refresh"
188 with m.Elif(cmd_buffer.source.valid):
189 with m.If(row_opened):
190 with m.If(row_hit):
191 m.d.comb += [
192 self.cmd.valid.eq(1),
193 self.cmd.cas.eq(1),
194 ]
195 with m.If(cmd_buffer.source.we):
196 m.d.comb += [
197 self.req.wdata_ready.eq(self.cmd.ready),
198 self.cmd.is_write.eq(1),
199 self.cmd.we.eq(1),
200 ]
201 with m.Else():
202 m.d.comb += [
203 self.req.rdata_valid.eq(self.cmd.ready),
204 self.cmd.is_read.eq(1),
205 ]
206 with m.If(self.cmd.ready & auto_precharge):
207 m.next = "Autoprecharge"
208 with m.Else():
209 m.next = "Precharge"
210 with m.Else():
211 m.next = "Activate"
212
213 with m.State("Precharge"):
214 m.d.comb += row_close.eq(1)
215
216 with m.If(twtpcon.ready & trascon.ready):
217 m.d.comb += [
218 self.cmd.valid.eq(1),
219 self.cmd.ras.eq(1),
220 self.cmd.we.eq(1),
221 self.cmd.is_cmd.eq(1),
222 ]
223
224 with m.If(self.cmd.ready):
225 m.next = "tRP"
226
227 with m.State("Autoprecharge"):
228 m.d.comb += row_close.eq(1)
229
230 with m.If(twtpcon.ready & trascon.ready):
231 m.next = "tRP"
232
233 with m.State("Activate"):
234 with m.If(trccon.ready):
235 m.d.comb += [
236 row_col_n_addr_sel.eq(1),
237 row_open.eq(1),
238 self.cmd.valid.eq(1),
239 self.cmd.is_cmd.eq(1),
240 self.cmd.ras.eq(1),
241 ]
242 with m.If(self.cmd.ready):
243 m.next = "tRCD"
244
245 with m.State("Refresh"):
246 m.d.comb += [
247 row_close.eq(1),
248 self.cmd.is_cmd.eq(1),
249 ]
250
251 with m.If(twtpcon.ready):
252 m.d.comb += self.refresh_gnt.eq(1)
253 with m.If(~self.refresh_req):
254 m.next = "Regular"
255
256 delayed_enter(m, "tRP", "Activate", self.settings.timing.tRP - 1)
257 delayed_enter(m, "tRCD", "Regular", self.settings.timing.tRCD - 1)
258
259 return m