|
1 | 1 | from amaranth import * |
2 | 2 |
|
3 | | -from transactron.core import Method, Methods, TModule, def_method, def_methods |
| 3 | +from transactron.core import Method, Methods, TModule, def_method, def_methods, Provided |
4 | 4 | from transactron.utils.amaranth_ext.elaboratables import MultiPriorityEncoder |
5 | 5 | from amaranth.lib.data import ArrayLayout |
6 | 6 |
|
| 7 | +from transactron.utils.amaranth_ext.functions import mod_add |
7 | 8 |
|
8 | | -__all__ = ["PriorityEncoderAllocator"] |
| 9 | + |
| 10 | +__all__ = ["PriorityEncoderAllocator", "PreservedOrderAllocator", "CircularAllocator"] |
9 | 11 |
|
10 | 12 |
|
11 | 13 | class PriorityEncoderAllocator(Elaboratable): |
@@ -129,3 +131,162 @@ def _(): |
129 | 131 | return {"used": used, "order": order} |
130 | 132 |
|
131 | 133 | return m |
| 134 | + |
| 135 | + |
| 136 | +class CircularAllocator(Elaboratable): |
| 137 | + """Circular allocator. |
| 138 | +
|
| 139 | + Allows to allocate and deallocate identifiers in FIFO order. It is |
| 140 | + possible to allocate or deallocate multiple identifiers in a single |
| 141 | + clock cycle. |
| 142 | + """ |
| 143 | + |
| 144 | + alloc: Provided[Method] |
| 145 | + """ |
| 146 | + Allocates new identifiers. Ready only if there are free identifiers |
| 147 | + available. The `count` argument must be less or equal to the number |
| 148 | + of available free identifiers. |
| 149 | +
|
| 150 | + If `with_validate_arguments` is false, invalid calls are allowed but can |
| 151 | + result in illegal state. |
| 152 | +
|
| 153 | + Parameters |
| 154 | + ---------- |
| 155 | + count: range(max_alloc + 1) |
| 156 | + The number of identifiers to allocate. |
| 157 | +
|
| 158 | + Returns |
| 159 | + ------- |
| 160 | + idents: ArrayLayout(range(entries), max_alloc) |
| 161 | + Array of allocated identifiers. |
| 162 | + new_end_idx: range(entries) |
| 163 | + First identifier after the last allocated one. |
| 164 | + """ |
| 165 | + |
| 166 | + free: Provided[Method] |
| 167 | + """ |
| 168 | + Frees previously allocated identifiers. Ready only if there are allocated |
| 169 | + identifiers. The `count` argument must be less or equal to the number of |
| 170 | + allocated identifiers. |
| 171 | +
|
| 172 | + If `with_validate_arguments` is false, invalid calls are allowed but can |
| 173 | + result in illegal state. |
| 174 | +
|
| 175 | + Parameters |
| 176 | + ---------- |
| 177 | + count: range(max_free + 1) |
| 178 | + The number of identifiers to deallocate. |
| 179 | +
|
| 180 | + Returns |
| 181 | + ------- |
| 182 | + idents: ArrayLayout(range(entries), max_alloc) |
| 183 | + Array of freed identifiers. |
| 184 | + new_start_idx: range(entries) |
| 185 | + First identifier after the last freed one. |
| 186 | + """ |
| 187 | + |
| 188 | + clear: Provided[Method] |
| 189 | + """ |
| 190 | + Restores the allocator to its initial state. |
| 191 | + """ |
| 192 | + |
| 193 | + start_idx: Signal |
| 194 | + """ |
| 195 | + First pointer of the circular allocator. The oldest allocated identifier, |
| 196 | + if one exists. |
| 197 | + """ |
| 198 | + |
| 199 | + end_idx: Signal |
| 200 | + """ |
| 201 | + Second pointer of the circular allocator. The first after the newest |
| 202 | + allocated identifier, if one exists. |
| 203 | + """ |
| 204 | + |
| 205 | + allocated: Signal |
| 206 | + """ |
| 207 | + The number of allocated identifiers. |
| 208 | + """ |
| 209 | + |
| 210 | + def __init__(self, entries: int, max_alloc: int = 1, max_free: int = 1, *, with_validate_arguments=True): |
| 211 | + """ |
| 212 | + Parameters |
| 213 | + ---------- |
| 214 | + entries: int |
| 215 | + The total number of identifiers available for allocation. |
| 216 | + max_alloc: int, optional |
| 217 | + The amount of identifiers that can be allocated in a single cycle. |
| 218 | + Defaults to 1. |
| 219 | + max_free: int, optional |
| 220 | + The amount of identifiers that can be freed in a single cycle. |
| 221 | + Defaults to 1. |
| 222 | + with_validate_arguments: bool, optional |
| 223 | + If true, `alloc` and `free` methods are guarded by argument |
| 224 | + validation so that it is impossible to put the allocator into |
| 225 | + an illegal state. Otherwise, the `count` argument needs to |
| 226 | + be verified using external logic. |
| 227 | + Defaults to true. |
| 228 | + """ |
| 229 | + self.entries = entries |
| 230 | + self.max_alloc = max_alloc |
| 231 | + self.max_free = max_free |
| 232 | + self.with_validate_arguments = with_validate_arguments |
| 233 | + |
| 234 | + self.alloc = Method( |
| 235 | + i=[("count", range(max_alloc + 1))], |
| 236 | + o=[("idents", ArrayLayout(range(entries), max_alloc)), ("new_end_idx", range(entries))], |
| 237 | + ) |
| 238 | + self.free = Method( |
| 239 | + i=[("count", range(max_free + 1))], |
| 240 | + o=[("idents", ArrayLayout(range(entries), max_free)), ("new_start_idx", range(entries))], |
| 241 | + ) |
| 242 | + self.clear = Method() |
| 243 | + |
| 244 | + self.start_idx = Signal(range(entries)) |
| 245 | + self.end_idx = Signal(range(entries)) |
| 246 | + self.allocated = Signal(range(entries + 1)) |
| 247 | + |
| 248 | + def elaborate(self, platform): |
| 249 | + m = TModule() |
| 250 | + |
| 251 | + alloc_count = Signal(range(self.max_alloc + 1)) |
| 252 | + free_count = Signal(range(self.max_free + 1)) |
| 253 | + |
| 254 | + m.d.sync += self.allocated.eq(self.allocated + alloc_count - free_count) |
| 255 | + |
| 256 | + kwargs = {} |
| 257 | + if self.with_validate_arguments and self.max_alloc > 1: |
| 258 | + kwargs["validate_arguments"] = lambda count: self.allocated + count <= self.entries |
| 259 | + |
| 260 | + @def_method(m, self.alloc, ready=self.allocated != self.entries, **kwargs) |
| 261 | + def _(count): |
| 262 | + new_end_idx = Signal.like(self.end_idx) |
| 263 | + m.d.av_comb += new_end_idx.eq(mod_add(self.end_idx, self.entries, count, self.max_alloc)) |
| 264 | + m.d.sync += self.end_idx.eq(new_end_idx) |
| 265 | + m.d.comb += alloc_count.eq(count) |
| 266 | + return { |
| 267 | + "idents": [mod_add(self.end_idx, self.entries, i, i) for i in range(self.max_alloc)], |
| 268 | + "new_end_idx": new_end_idx, |
| 269 | + } |
| 270 | + |
| 271 | + kwargs = {} |
| 272 | + if self.with_validate_arguments and self.max_free > 1: |
| 273 | + kwargs["validate_arguments"] = lambda count: count <= self.allocated |
| 274 | + |
| 275 | + @def_method(m, self.free, ready=self.allocated != 0, **kwargs) |
| 276 | + def _(count): |
| 277 | + new_start_idx = Signal.like(self.start_idx) |
| 278 | + m.d.av_comb += new_start_idx.eq(mod_add(self.start_idx, self.entries, count, self.max_free)) |
| 279 | + m.d.sync += self.start_idx.eq(new_start_idx) |
| 280 | + m.d.comb += free_count.eq(count) |
| 281 | + return { |
| 282 | + "idents": [mod_add(self.start_idx, self.entries, i, i) for i in range(self.max_free)], |
| 283 | + "new_start_idx": new_start_idx, |
| 284 | + } |
| 285 | + |
| 286 | + @def_method(m, self.clear) |
| 287 | + def _(): |
| 288 | + m.d.sync += self.start_idx.eq(0) |
| 289 | + m.d.sync += self.end_idx.eq(0) |
| 290 | + m.d.sync += self.allocated.eq(0) |
| 291 | + |
| 292 | + return m |
0 commit comments