1 # nmigen: UnusedElaboratable=no
6 from nmigen
.test
.utils
import *
7 from nmigen
.asserts
import *
8 from nmigen
.utils
import log2_int
10 from nmigen_soc
import wishbone
12 from ..periph
.sdram
import *
13 from ..cores
import litedram
15 from .utils
.wishbone
import WishboneSubordinateSpec
16 from .utils
.formal
import FormalTestCase
19 class WritebackCacheSpec(Elaboratable
):
20 def __init__(self
, dut
):
23 def elaborate(self
, platform
):
26 m
.submodules
.dut
= dut
= self
.dut
30 m
.submodules
.wb_sub_spec
= WishboneSubordinateSpec(self
.dut
.intr_bus
)
32 ratio
= dut
.dram_port
.data_width
// dut
.intr_bus
.data_width
33 nb_lines
= (dut
.size
* dut
.intr_bus
.granularity
) // dut
.dram_port
.data_width
34 intr_bus_adr
= Record([
35 ("offset", log2_int(ratio
)),
36 ("line", log2_int(nb_lines
)),
37 ("tag", dut
.intr_bus
.addr_width
- log2_int(nb_lines
) - log2_int(ratio
)),
40 Assert(len(intr_bus_adr
) == len(dut
.intr_bus
.adr
)),
41 intr_bus_adr
.eq(dut
.intr_bus
.adr
),
44 dram_spec_addr
= AnyConst(dut
.dram_port
.addr_width
)
45 dram_spec_data
= Signal(dut
.dram_port
.data_width
)
46 dram_spec_line
= Array(
47 Signal(dut
.intr_bus
.data_width
, name
=f
"dram_spec_word_{i}")
51 m
.d
.comb
+= dram_spec_data
.eq(Cat(dram_spec_line
))
54 m
.d
.comb
+= Assume(dram_spec_data
== 0)
56 intr_dram_addr
= Signal
.like(dram_spec_addr
)
57 m
.d
.comb
+= intr_dram_addr
.eq(Cat(intr_bus_adr
.line
, intr_bus_adr
.tag
))
59 with m
.If(dut
.intr_bus
.cyc
& dut
.intr_bus
.stb
& dut
.intr_bus
.ack
60 & (intr_dram_addr
== dram_spec_addr
)
62 dram_spec_word
= dram_spec_line
[intr_bus_adr
.offset
]
63 for i
, sel_bit
in enumerate(dut
.intr_bus
.sel
):
64 dram_spec_bits
= dram_spec_word
.word_select(i
, dut
.intr_bus
.granularity
)
65 intr_data_bits
= dut
.intr_bus
.dat_w
.word_select(i
, dut
.intr_bus
.granularity
)
67 m
.d
.sync
+= dram_spec_bits
.eq(intr_data_bits
)
69 # * A cache hit at `dram_spec_addr` must be coherent with `dram_spec_data`.
71 with m
.If(dut
.intr_bus
.cyc
& dut
.intr_bus
.stb
& dut
.intr_bus
.ack
72 & (intr_dram_addr
== dram_spec_addr
)):
73 dram_spec_word
= dram_spec_line
[intr_bus_adr
.offset
]
74 m
.d
.comb
+= Assert(dut
.intr_bus
.dat_r
== dram_spec_word
)
79 ("addr", dut
.dram_port
.addr_width
),
83 ("r", dut
.dram_port
.data_width
),
84 ("w", dut
.dram_port
.data_width
),
93 m
.d
.comb
+= Assume(~dram_done
.any())
95 with m
.If(dut
.dram_port
.cmd
.valid
& dut
.dram_port
.cmd
.ready
):
98 dram_cmd
.addr
.eq(dut
.dram_port
.cmd
.addr
),
99 dram_cmd
.we
.eq(dut
.dram_port
.cmd
.we
),
101 with m
.If(dut
.dram_port
.w
.valid
& dut
.dram_port
.w
.ready
):
104 dram_data
.w
.eq(dut
.dram_port
.w
.data
),
106 with m
.If(dut
.dram_port
.r
.ready
& dut
.dram_port
.r
.valid
):
109 dram_data
.r
.eq(dut
.dram_port
.r
.data
),
112 with m
.If(dram_done
.cmd
& dram_done
.r
):
113 with m
.If(dram_cmd
.addr
== dram_spec_addr
):
114 m
.d
.comb
+= Assume(dram_data
.r
== dram_spec_data
)
116 # Some of the following constraints are tighter than what the LiteDRAM native interface
117 # actually allows. We may relax them in the future to improve cache performance.
119 # * A new command must not be initiated before the previous one has completed.
121 with m
.If(dut
.dram_port
.cmd
.valid
):
122 m
.d
.comb
+= Assert(~dram_done
.cmd
)
123 with m
.If(dut
.dram_port
.w
.valid
):
124 m
.d
.comb
+= Assert(~dram_done
.w
)
125 with m
.If(dut
.dram_port
.r
.ready
):
126 m
.d
.comb
+= Assert(~dram_done
.r
)
128 # * A command may either be a read or a write, but not both.
130 with m
.If(dram_done
.cmd
):
131 with m
.If(dram_cmd
.we
):
132 m
.d
.comb
+= Assert(~dram_done
.r
)
134 m
.d
.comb
+= Assert(~dram_done
.w
)
137 Assert(dram_done
.r
.implies(~dram_done
.w
)),
138 Assert(dram_done
.w
.implies(~dram_done
.r
)),
141 # * A read command consists of:
142 # - a transaction on the `dram.cmd` stream with `dram.cmd.we` de-asserted;
143 # - a transaction on the `dram.r` stream.
145 with m
.If(dram_done
.cmd
& dram_done
.r
):
146 m
.d
.comb
+= Assert(~dram_cmd
.we
)
152 # * A write command consists of:
153 # - a transaction on the `dram.cmd` stream with `dram.cmd.we` asserted;
154 # - a transaction on the `dram.w` stream.
156 with m
.If(dram_done
.cmd
& dram_done
.w
):
157 m
.d
.comb
+= Assert(dram_cmd
.we
)
163 # * A DRAM write at `dram_spec_addr` must be coherent with `dram_spec_data`.
165 with m
.If(dram_done
.cmd
& dram_done
.w
):
166 with m
.If(dram_cmd
.addr
== dram_spec_addr
):
167 m
.d
.comb
+= Assert(dram_data
.w
== dram_spec_data
)
169 # For fairness, assume that any stream transaction completes in at most 3 clock cycles.
177 with m
.If(dut
.dram_port
.cmd
.valid
& ~dut
.dram_port
.cmd
.ready
):
178 m
.d
.sync
+= dram_wait
.cmd
.eq(dram_wait
.cmd
+ 1)
180 m
.d
.sync
+= dram_wait
.cmd
.eq(0)
182 with m
.If(dut
.dram_port
.w
.valid
& ~dut
.dram_port
.w
.ready
):
183 m
.d
.sync
+= dram_wait
.w
.eq(dram_wait
.w
+ 1)
185 m
.d
.sync
+= dram_wait
.w
.eq(0)
187 with m
.If(dut
.dram_port
.r
.ready
& ~dut
.dram_port
.r
.valid
):
188 m
.d
.sync
+= dram_wait
.r
.eq(dram_wait
.r
+ 1)
190 m
.d
.sync
+= dram_wait
.r
.eq(0)
193 Assume(dram_wait
.cmd
< 2),
194 Assume(dram_wait
.r
< 2),
195 Assume(dram_wait
.w
< 2),
201 class WritebackCacheTestCase(FormalTestCase
):
202 def test_wrong_dram_port(self
):
203 with self
.assertRaisesRegex(TypeError,
204 r
"DRAM port must be an instance of lambdasoc\.cores\.litedram\.NativePort, "
206 WritebackCache(dram_port
="foo", size
=8, data_width
=16)
208 def test_wrong_size(self
):
209 dram_port
= litedram
.NativePort(addr_width
=23, data_width
=32)
210 with self
.assertRaisesRegex(ValueError,
211 r
"Cache size must be a positive power of two integer, not 'foo'"):
212 WritebackCache(dram_port
, size
="foo", data_width
=16)
213 with self
.assertRaisesRegex(ValueError,
214 r
"Cache size must be a positive power of two integer, not -1"):
215 WritebackCache(dram_port
, size
=-1, data_width
=16)
216 with self
.assertRaisesRegex(ValueError,
217 r
"Cache size must be a positive power of two integer, not 42"):
218 WritebackCache(dram_port
, size
=42, data_width
=16)
220 def test_wrong_data_width(self
):
221 dram_port
= litedram
.NativePort(addr_width
=23, data_width
=32)
222 with self
.assertRaisesRegex(ValueError,
223 r
"Data width must be a positive power of two integer, not 'foo'"):
224 WritebackCache(dram_port
, size
=8, data_width
="foo")
225 with self
.assertRaisesRegex(ValueError,
226 r
"Data width must be a positive power of two integer, not -1"):
227 WritebackCache(dram_port
, size
=8, data_width
=-1)
228 with self
.assertRaisesRegex(ValueError,
229 r
"Data width must be a positive power of two integer, not 42"):
230 WritebackCache(dram_port
, size
=8, data_width
=42)
232 def test_wrong_ratio(self
):
233 dram_port
= litedram
.NativePort(addr_width
=23, data_width
=32)
234 with self
.assertRaisesRegex(ValueError,
235 r
"DRAM port data width must be a multiple of data width, but 32 is not a multiple "
237 WritebackCache(dram_port
, size
=8, data_width
=64)
239 def check(self
, dut
):
241 m
.domains
.sync
= ClockDomain(reset_less
=True)
242 m
.submodules
.spec
= WritebackCacheSpec(dut
)
243 self
.assertFormal(m
, mode
="hybrid", depth
=10)
245 def test_spec_simple(self
):
246 dram_port
= litedram
.NativePort(addr_width
=23, data_width
=32)
247 dut
= WritebackCache(dram_port
, size
=8, data_width
=16, granularity
=8)
251 class SDRAMPeripheralTestCase(unittest
.TestCase
):
252 def test_wrong_core(self
):
253 with self
.assertRaisesRegex(TypeError,
254 r
"LiteDRAM core must be an instance of lambdasoc\.cores\.litedram\.Core, "
256 sdram
= SDRAMPeripheral(core
="foo", cache_size
=8)