gram.core.bankmachine: Add comment for address slicers
[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 import math
7
8 from nmigen import *
9
10 from gram.common import *
11 from gram.core.multiplexer import *
12 from gram.compat import delayed_enter
13 import gram.stream as stream
14
15 __ALL__ = ["BankMachine"]
16
17 class _AddressSlicer(Elaboratable):
18 def __init__(self, addrbits, colbits, address_align):
19 self._address_align = address_align
20 self._split = colbits - address_align
21
22 self.address = Signal(addrbits)
23 self.row = Signal(addrbits-self._split)
24 self.col = Signal(address_align+self._split)
25
26 def elaborate(self, platform):
27 m = Module()
28
29 m.d.comb += [
30 self.row.eq(self.address[self._split:]),
31 self.col.eq(Cat(Repl(0, self._address_align), self.address[:self._split]))
32 ]
33
34 return m
35
36 class BankMachine(Elaboratable):
37 """Converts requests from ports into DRAM commands
38
39 BankMachine abstracts single DRAM bank by keeping track of the currently
40 selected row. It converts requests from gramCrossbar to targetted
41 to that bank into DRAM commands that go to the Multiplexer, inserting any
42 needed activate/precharge commands (with optional auto-precharge). It also
43 keeps track and enforces some DRAM timings (other timings are enforced in
44 the Multiplexer).
45
46 BankMachines work independently from the data path (which connects
47 gramCrossbar with the Multiplexer directly).
48
49 Stream of requests from gramCrossbar is being queued, so that reqeust
50 can be "looked ahead", and auto-precharge can be performed (if enabled in
51 settings).
52
53 Lock (cmd_layout.lock) is used to synchronise with gramCrossbar. It is
54 being held when:
55 - there is a valid command awaiting in `cmd_buffer_lookahead` - this buffer
56 becomes ready simply when the next data gets fetched to the `cmd_buffer`
57 - there is a valid command in `cmd_buffer` - `cmd_buffer` becomes ready
58 when the BankMachine sends wdata_ready/rdata_valid back to the crossbar
59
60 Parameters
61 ----------
62 n : int
63 Bank number
64 address_width : int
65 LiteDRAMInterface address width
66 address_align : int
67 Address alignment depending on burst length
68 nranks : int
69 Number of separate DRAM chips (width of chip select)
70 settings : ControllerSettings
71 LiteDRAMController settings
72
73 Attributes
74 ----------
75 req : Record(cmd_layout)
76 Stream of requests from gramCrossbar
77 refresh_req : Signal(), in
78 Indicates that refresh needs to be done, connects to Refresher.cmd.valid
79 refresh_gnt : Signal(), out
80 Indicates that refresh permission has been granted, satisfying timings
81 cmd : Endpoint(cmd_request_rw_layout)
82 Stream of commands to the Multiplexer
83 """
84
85 def __init__(self, n, address_width, address_align, nranks, settings):
86 self.settings = settings
87 self.req = req = Record(cmd_layout(address_width))
88 self.refresh_req = Signal()
89 self.refresh_gnt = Signal()
90
91 a = settings.geom.addressbits
92 ba = settings.geom.bankbits + log2_int(nranks)
93 self.cmd = stream.Endpoint(cmd_request_rw_layout(a, ba))
94
95 self._address_align = address_align
96 self._n = n
97
98 def elaborate(self, platform):
99 m = Module()
100
101 auto_precharge = Signal()
102
103 # Command buffer ---------------------------------------------------------------------------
104 cmd_buffer_layout = [("we", 1), ("addr", len(self.req.addr))]
105 cmd_buffer_lookahead = stream.SyncFIFO(
106 cmd_buffer_layout, self.settings.cmd_buffer_depth,
107 buffered=self.settings.cmd_buffer_buffered)
108 # 1 depth buffer to detect row change
109 cmd_buffer = stream.Buffer(cmd_buffer_layout)
110 m.submodules += cmd_buffer_lookahead, cmd_buffer
111 m.d.comb += [
112 cmd_buffer_lookahead.sink.valid.eq(self.req.valid),
113 self.req.ready.eq(cmd_buffer_lookahead.sink.ready),
114 cmd_buffer_lookahead.sink.payload.we.eq(self.req.we),
115 cmd_buffer_lookahead.sink.payload.addr.eq(self.req.addr),
116 cmd_buffer_lookahead.source.connect(cmd_buffer.sink),
117 cmd_buffer.source.ready.eq(self.req.wdata_ready | self.req.rdata_valid),
118 self.req.lock.eq(cmd_buffer_lookahead.source.valid | cmd_buffer.source.valid),
119 ]
120
121 # Address slicers
122 m.submodules.lookahead_slicer = lookahead_slicer = _AddressSlicer(len(cmd_buffer_lookahead.source.addr),
123 self.settings.geom.colbits, self._address_align)
124 m.submodules.current_slicer = current_slicer = _AddressSlicer(len(cmd_buffer.source.addr),
125 self.settings.geom.colbits, self._address_align)
126 m.d.comb += [
127 current_slicer.address.eq(cmd_buffer.source.addr),
128 lookahead_slicer.address.eq(cmd_buffer_lookahead.source.addr),
129 ]
130
131 # Row tracking -----------------------------------------------------------------------------
132 row = Signal(self.settings.geom.rowbits)
133 row_opened = Signal()
134 row_hit = Signal()
135 row_open = Signal()
136 row_close = Signal()
137 m.d.comb += row_hit.eq(row == current_slicer.row)
138 with m.If(row_close):
139 m.d.sync += row_opened.eq(0)
140 with m.Elif(row_open):
141 m.d.sync += [
142 row_opened.eq(1),
143 row.eq(current_slicer.row),
144 ]
145
146 # Address generation -----------------------------------------------------------------------
147 row_col_n_addr_sel = Signal()
148 m.d.comb += self.cmd.ba.eq(self._n)
149 with m.If(row_col_n_addr_sel):
150 m.d.comb += self.cmd.a.eq(current_slicer.row)
151 with m.Else():
152 m.d.comb += self.cmd.a.eq((auto_precharge << 10) | current_slicer.col)
153
154 # tWTP / tRC / tRAS controllers
155 write_latency = math.ceil(self.settings.phy.cwl / self.settings.phy.nphases)
156 precharge_time = write_latency + self.settings.timing.tWR + self.settings.timing.tCCD # AL=0
157 m.submodules.twtpcon = twtpcon = tXXDController(precharge_time)
158 m.d.comb += twtpcon.valid.eq(self.cmd.valid & self.cmd.ready & self.cmd.is_write)
159
160 m.submodules.trccon = trccon = tXXDController(self.settings.timing.tRC)
161 m.submodules.trascon = trascon = tXXDController(self.settings.timing.tRAS)
162 valid_ready_row_open = Signal()
163 m.d.comb += [
164 valid_ready_row_open.eq(self.cmd.valid & self.cmd.ready & row_open),
165 trccon.valid.eq(valid_ready_row_open),
166 trascon.valid.eq(valid_ready_row_open),
167 ]
168
169 # Auto Precharge generation ----------------------------------------------------------------
170 # generate auto precharge when current and next cmds are to different rows
171 if self.settings.with_auto_precharge:
172 with m.If(cmd_buffer_lookahead.source.valid & cmd_buffer.source.valid):
173 with m.If(lookahead_slicer.row != current_slicer.row):
174 m.d.comb += auto_precharge.eq(row_close == 0)
175
176 # Control and command generation FSM -------------------------------------------------------
177 # Note: tRRD, tFAW, tCCD, tWTR timings are enforced by the multiplexer
178 with m.FSM():
179 with m.State("Regular"):
180 with m.If(self.refresh_req):
181 with m.If(row_opened):
182 m.next = "Precharge-For-Refresh"
183 with m.Else():
184 m.next = "Refresh"
185 with m.Elif(cmd_buffer.source.valid):
186 with m.If(row_opened):
187 with m.If(row_hit):
188 m.d.comb += [
189 self.cmd.valid.eq(1),
190 self.cmd.cas.eq(1),
191 ]
192 with m.If(cmd_buffer.source.we):
193 m.d.comb += [
194 self.req.wdata_ready.eq(self.cmd.ready),
195 self.cmd.is_write.eq(1),
196 self.cmd.we.eq(1),
197 ]
198 with m.Else():
199 m.d.comb += [
200 self.req.rdata_valid.eq(self.cmd.ready),
201 self.cmd.is_read.eq(1),
202 ]
203 with m.If(self.cmd.ready & auto_precharge):
204 m.next = "Autoprecharge"
205 with m.Else():
206 m.next = "Precharge"
207 with m.Else():
208 m.next = "Activate"
209
210 with m.State("Precharge"):
211 m.d.comb += row_close.eq(1)
212
213 with m.If(twtpcon.ready & trascon.ready):
214 m.d.comb += [
215 self.cmd.valid.eq(1),
216 self.cmd.ras.eq(1),
217 self.cmd.we.eq(1),
218 self.cmd.is_cmd.eq(1),
219 ]
220
221 with m.If(self.cmd.ready):
222 m.next = "tRP"
223
224 with m.State("Precharge-For-Refresh"):
225 m.d.comb += row_close.eq(1)
226
227 with m.If(twtpcon.ready & trascon.ready):
228 m.d.comb += [
229 self.cmd.valid.eq(1),
230 self.cmd.ras.eq(1),
231 self.cmd.we.eq(1),
232 self.cmd.is_cmd.eq(1),
233 ]
234
235 with m.If(self.cmd.ready):
236 m.next = "Refresh"
237
238 with m.State("Autoprecharge"):
239 m.d.comb += row_close.eq(1)
240
241 with m.If(twtpcon.ready & trascon.ready):
242 m.next = "tRP"
243
244 with m.State("Activate"):
245 with m.If(trccon.ready):
246 m.d.comb += [
247 row_col_n_addr_sel.eq(1),
248 row_open.eq(1),
249 self.cmd.valid.eq(1),
250 self.cmd.is_cmd.eq(1),
251 self.cmd.ras.eq(1),
252 ]
253 with m.If(self.cmd.ready):
254 m.next = "tRCD"
255
256 with m.State("Refresh"):
257 m.d.comb += [
258 row_close.eq(1),
259 self.cmd.is_cmd.eq(1),
260 ]
261
262 with m.If(twtpcon.ready):
263 m.d.comb += self.refresh_gnt.eq(1)
264 with m.If(~self.refresh_req):
265 m.next = "Regular"
266
267 delayed_enter(m, "tRP", "Activate", self.settings.timing.tRP - 1)
268 delayed_enter(m, "tRCD", "Regular", self.settings.timing.tRCD - 1)
269
270 return m