Rename VCD file output
[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(
133 self.settings.geom.colbits, self._address_align)
134
135 # Row tracking -----------------------------------------------------------------------------
136 row = Signal(self.settings.geom.rowbits)
137 row_opened = Signal()
138 row_hit = Signal()
139 row_open = Signal()
140 row_close = 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):
145 m.d.sync += [
146 row_opened.eq(1),
147 row.eq(slicer.row(cmd_buffer.source.addr)),
148 ]
149
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))
155 with m.Else():
156 m.d.comb += self.cmd.a.eq((auto_precharge << 10)
157 | slicer.col(cmd_buffer.source.addr))
158
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)
167
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)
171
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)
175
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)
182
183 # Control and command generation FSM -------------------------------------------------------
184 # Note: tRRD, tFAW, tCCD, tWTR timings are enforced by the multiplexer
185 with m.FSM():
186 with m.State("Regular"):
187 with m.If(self.refresh_req):
188 m.next = "Refresh"
189 with m.Elif(cmd_buffer.source.valid):
190 with m.If(row_opened):
191 with m.If(row_hit):
192 m.d.comb += [
193 self.cmd.valid.eq(1),
194 self.cmd.cas.eq(1),
195 ]
196 with m.If(cmd_buffer.source.we):
197 m.d.comb += [
198 self.req.wdata_ready.eq(self.cmd.ready),
199 self.cmd.is_write.eq(1),
200 self.cmd.we.eq(1),
201 ]
202 with m.Else():
203 m.d.comb += [
204 self.req.rdata_valid.eq(self.cmd.ready),
205 self.cmd.is_read.eq(1),
206 ]
207 with m.If(self.cmd.ready & auto_precharge):
208 m.next = "Autoprecharge"
209 with m.Else():
210 m.next = "Precharge"
211 with m.Else():
212 m.next = "Activate"
213
214 with m.State("Precharge"):
215 m.d.comb += row_close.eq(1)
216
217 with m.If(twtpcon.ready & trascon.ready):
218 m.d.comb += [
219 self.cmd.valid.eq(1),
220 self.cmd.ras.eq(1),
221 self.cmd.we.eq(1),
222 self.cmd.is_cmd.eq(1),
223 ]
224
225 with m.If(self.cmd.ready):
226 m.next = "tRP"
227
228 with m.State("Autoprecharge"):
229 m.d.comb += row_close.eq(1)
230
231 with m.If(twtpcon.ready & trascon.ready):
232 m.next = "tRP"
233
234 with m.State("Activate"):
235 with m.If(trccon.ready):
236 m.d.comb += [
237 row_col_n_addr_sel.eq(1),
238 row_open.eq(1),
239 self.cmd.valid.eq(1),
240 self.cmd.is_cmd.eq(1),
241 self.cmd.ras.eq(1),
242 ]
243 with m.If(self.cmd.ready):
244 m.next = "tRCD"
245
246 with m.State("Refresh"):
247 m.d.comb += [
248 row_close.eq(1),
249 self.cmd.is_cmd.eq(1),
250 ]
251
252 with m.If(twtpcon.ready):
253 m.d.comb += self.refresh_gnt.eq(1)
254 with m.If(~self.refresh_req):
255 m.next = "Regular"
256
257 delayed_enter(m, "tRP", "Activate", self.settings.timing.tRP - 1)
258 delayed_enter(m, "tRCD", "Regular", self.settings.timing.tRCD - 1)
259
260 return m