add 3rd parameter to DummyALU
[soc.git] / src / soc / experiment / alu_hier.py
1 """*Experimental* ALU: based on nmigen alu_hier.py, includes branch-compare ALU
2
3 This ALU is *deliberately* designed to add in (unnecessary) delays into
4 different operations so as to be able to test the 6600-style matrices
5 and the CompUnits. Countdown timers wait for (defined) periods before
6 indicating that the output is valid
7
8 A "real" integer ALU would place the answers onto the output bus after
9 only one cycle (sync)
10 """
11
12 from nmigen import Elaboratable, Signal, Module, Const, Mux, Array
13 from nmigen.hdl.rec import Record, Layout
14 from nmigen.cli import main
15 from nmigen.cli import verilog, rtlil
16 from nmigen.compat.sim import run_simulation
17
18 from soc.decoder.power_enums import InternalOp, Function, CryIn
19
20 from soc.fu.alu.alu_input_record import CompALUOpSubset
21 from soc.fu.cr.cr_input_record import CompCROpSubset
22
23 import operator
24
25
26
27
28 class Adder(Elaboratable):
29 def __init__(self, width):
30 self.invert_a = Signal()
31 self.a = Signal(width)
32 self.b = Signal(width)
33 self.o = Signal(width)
34
35 def elaborate(self, platform):
36 m = Module()
37 with m.If(self.invert_a):
38 m.d.comb += self.o.eq((~self.a) + self.b)
39 with m.Else():
40 m.d.comb += self.o.eq(self.a + self.b)
41 return m
42
43
44 class Subtractor(Elaboratable):
45 def __init__(self, width):
46 self.a = Signal(width)
47 self.b = Signal(width)
48 self.o = Signal(width)
49
50 def elaborate(self, platform):
51 m = Module()
52 m.d.comb += self.o.eq(self.a - self.b)
53 return m
54
55
56 class Multiplier(Elaboratable):
57 def __init__(self, width):
58 self.a = Signal(width)
59 self.b = Signal(width)
60 self.o = Signal(width)
61
62 def elaborate(self, platform):
63 m = Module()
64 m.d.comb += self.o.eq(self.a * self.b)
65 return m
66
67
68 class Shifter(Elaboratable):
69 def __init__(self, width):
70 self.width = width
71 self.a = Signal(width)
72 self.b = Signal(width)
73 self.o = Signal(width)
74
75 def elaborate(self, platform):
76 m = Module()
77 btrunc = Signal(self.width)
78 m.d.comb += btrunc.eq(self.b & Const((1<<self.width)-1))
79 m.d.comb += self.o.eq(self.a >> btrunc)
80 return m
81
82 class Dummy:
83 pass
84
85 class DummyALU(Elaboratable):
86 def __init__(self, width):
87 self.p = Dummy() # make look like nmutil pipeline API
88 self.p.data_i = Dummy()
89 self.p.data_i.ctx = Dummy()
90 self.n = Dummy() # make look like nmutil pipeline API
91 self.n.data_o = Dummy()
92 self.p.valid_i = Signal()
93 self.p.ready_o = Signal()
94 self.n.ready_i = Signal()
95 self.n.valid_o = Signal()
96 self.counter = Signal(4)
97 self.op = CompCROpSubset()
98 i = []
99 i.append(Signal(width, name="i1"))
100 i.append(Signal(width, name="i2"))
101 i.append(Signal(width, name="i3"))
102 self.i = Array(i)
103 self.a, self.b, self.c = i[0], i[1], i[2]
104 self.out = Array([Signal(width)])
105 self.o = self.out[0]
106 self.width = width
107 # more "look like nmutil pipeline API"
108 self.p.data_i.ctx.op = self.op
109 self.p.data_i.a = self.a
110 self.p.data_i.b = self.b
111 self.p.data_i.c = self.c
112 self.n.data_o.o = self.o
113
114 def elaborate(self, platform):
115 m = Module()
116
117 go_now = Signal(reset_less=True) # testing no-delay ALU
118
119 with m.If(self.p.valid_i):
120 # input is valid. next check, if we already said "ready" or not
121 with m.If(~self.p.ready_o):
122 # we didn't say "ready" yet, so say so and initialise
123 m.d.sync += self.p.ready_o.eq(1)
124
125 m.d.sync += self.o.eq(self.a)
126 m.d.comb += go_now.eq(1)
127 m.d.sync += self.counter.eq(1)
128
129 with m.Else():
130 # input says no longer valid, so drop ready as well.
131 # a "proper" ALU would have had to sync in the opcode and a/b ops
132 m.d.sync += self.p.ready_o.eq(0)
133
134 # ok so the counter's running: when it gets to 1, fire the output
135 with m.If((self.counter == 1) | go_now):
136 # set the output as valid if the recipient is ready for it
137 m.d.sync += self.n.valid_o.eq(1)
138 with m.If(self.n.ready_i & self.n.valid_o):
139 m.d.sync += self.n.valid_o.eq(0)
140 # recipient said it was ready: reset back to known-good.
141 m.d.sync += self.counter.eq(0) # reset the counter
142 m.d.sync += self.o.eq(0) # clear the output for tidiness sake
143
144 # countdown to 1 (transition from 1 to 0 only on acknowledgement)
145 with m.If(self.counter > 1):
146 m.d.sync += self.counter.eq(self.counter - 1)
147
148 return m
149
150 def __iter__(self):
151 yield from self.op.ports()
152 yield self.a
153 yield self.b
154 yield self.c
155 yield self.o
156
157 def ports(self):
158 return list(self)
159
160
161 class ALU(Elaboratable):
162 def __init__(self, width):
163 self.p = Dummy() # make look like nmutil pipeline API
164 self.p.data_i = Dummy()
165 self.p.data_i.ctx = Dummy()
166 self.n = Dummy() # make look like nmutil pipeline API
167 self.n.data_o = Dummy()
168 self.p.valid_i = Signal()
169 self.p.ready_o = Signal()
170 self.n.ready_i = Signal()
171 self.n.valid_o = Signal()
172 self.counter = Signal(4)
173 self.op = CompALUOpSubset()
174 i = []
175 i.append(Signal(width, name="i1"))
176 i.append(Signal(width, name="i2"))
177 self.i = Array(i)
178 self.a, self.b = i[0], i[1]
179 self.out = Array([Signal(width)])
180 self.o = self.out[0]
181 self.width = width
182 # more "look like nmutil pipeline API"
183 self.p.data_i.ctx.op = self.op
184 self.p.data_i.a = self.a
185 self.p.data_i.b = self.b
186 self.n.data_o.o = self.o
187
188 def elaborate(self, platform):
189 m = Module()
190 add = Adder(self.width)
191 mul = Multiplier(self.width)
192 shf = Shifter(self.width)
193
194 m.submodules.add = add
195 m.submodules.mul = mul
196 m.submodules.shf = shf
197
198 # really should not activate absolutely all ALU inputs like this
199 for mod in [add, mul, shf]:
200 m.d.comb += [
201 mod.a.eq(self.a),
202 mod.b.eq(self.b),
203 ]
204
205 # pass invert (and carry later)
206 m.d.comb += add.invert_a.eq(self.op.invert_a)
207
208 go_now = Signal(reset_less=True) # testing no-delay ALU
209
210 with m.If(self.p.valid_i):
211 # input is valid. next check, if we already said "ready" or not
212 with m.If(~self.p.ready_o):
213 # we didn't say "ready" yet, so say so and initialise
214 m.d.sync += self.p.ready_o.eq(1)
215
216 # as this is a "fake" pipeline, just grab the output right now
217 with m.If(self.op.insn_type == InternalOp.OP_ADD):
218 m.d.sync += self.o.eq(add.o)
219 with m.Elif(self.op.insn_type == InternalOp.OP_MUL_L64):
220 m.d.sync += self.o.eq(mul.o)
221 with m.Elif(self.op.insn_type == InternalOp.OP_SHR):
222 m.d.sync += self.o.eq(shf.o)
223 # TODO: SUB
224
225 # NOTE: all of these are fake, just something to test
226
227 # MUL, to take 5 instructions
228 with m.If(self.op.insn_type == InternalOp.OP_MUL_L64):
229 m.d.sync += self.counter.eq(5)
230 # SHIFT to take 7
231 with m.Elif(self.op.insn_type == InternalOp.OP_SHR):
232 m.d.sync += self.counter.eq(7)
233 # ADD/SUB to take 2, straight away
234 with m.If(self.op.insn_type == InternalOp.OP_ADD):
235 m.d.sync += self.counter.eq(3)
236 # others to take 1, straight away
237 with m.Else():
238 m.d.comb += go_now.eq(1)
239 m.d.sync += self.counter.eq(1)
240
241 with m.Else():
242 # input says no longer valid, so drop ready as well.
243 # a "proper" ALU would have had to sync in the opcode and a/b ops
244 m.d.sync += self.p.ready_o.eq(0)
245
246 # ok so the counter's running: when it gets to 1, fire the output
247 with m.If((self.counter == 1) | go_now):
248 # set the output as valid if the recipient is ready for it
249 m.d.sync += self.n.valid_o.eq(1)
250 with m.If(self.n.ready_i & self.n.valid_o):
251 m.d.sync += self.n.valid_o.eq(0)
252 # recipient said it was ready: reset back to known-good.
253 m.d.sync += self.counter.eq(0) # reset the counter
254 m.d.sync += self.o.eq(0) # clear the output for tidiness sake
255
256 # countdown to 1 (transition from 1 to 0 only on acknowledgement)
257 with m.If(self.counter > 1):
258 m.d.sync += self.counter.eq(self.counter - 1)
259
260 return m
261
262 def __iter__(self):
263 yield from self.op.ports()
264 yield self.a
265 yield self.b
266 yield self.o
267
268 def ports(self):
269 return list(self)
270
271
272 class BranchOp(Elaboratable):
273 def __init__(self, width, op):
274 self.a = Signal(width)
275 self.b = Signal(width)
276 self.o = Signal(width)
277 self.op = op
278
279 def elaborate(self, platform):
280 m = Module()
281 m.d.comb += self.o.eq(Mux(self.op(self.a, self.b), 1, 0))
282 return m
283
284
285 class BranchALU(Elaboratable):
286 def __init__(self, width):
287 self.p = Dummy() # make look like nmutil pipeline API
288 self.p.data_i = Dummy()
289 self.p.data_i.ctx = Dummy()
290 self.n = Dummy() # make look like nmutil pipeline API
291 self.n.data_o = Dummy()
292 self.p.valid_i = Signal()
293 self.p.ready_o = Signal()
294 self.n.ready_i = Signal()
295 self.n.valid_o = Signal()
296 self.counter = Signal(4)
297 self.op = Signal(2)
298 i = []
299 i.append(Signal(width, name="i1"))
300 i.append(Signal(width, name="i2"))
301 self.i = Array(i)
302 self.a, self.b = i[0], i[1]
303 self.out = Array([Signal(width)])
304 self.o = self.out[0]
305 self.width = width
306
307 def elaborate(self, platform):
308 m = Module()
309 bgt = BranchOp(self.width, operator.gt)
310 blt = BranchOp(self.width, operator.lt)
311 beq = BranchOp(self.width, operator.eq)
312 bne = BranchOp(self.width, operator.ne)
313
314 m.submodules.bgt = bgt
315 m.submodules.blt = blt
316 m.submodules.beq = beq
317 m.submodules.bne = bne
318 for mod in [bgt, blt, beq, bne]:
319 m.d.comb += [
320 mod.a.eq(self.a),
321 mod.b.eq(self.b),
322 ]
323
324 go_now = Signal(reset_less=True) # testing no-delay ALU
325 with m.If(self.p.valid_i):
326 # input is valid. next check, if we already said "ready" or not
327 with m.If(~self.p.ready_o):
328 # we didn't say "ready" yet, so say so and initialise
329 m.d.sync += self.p.ready_o.eq(1)
330
331 # as this is a "fake" pipeline, just grab the output right now
332 with m.Switch(self.op):
333 for i, mod in enumerate([bgt, blt, beq, bne]):
334 with m.Case(i):
335 m.d.sync += self.o.eq(mod.o)
336 m.d.sync += self.counter.eq(5) # branch to take 5 cycles (fake)
337 #m.d.comb += go_now.eq(1)
338 with m.Else():
339 # input says no longer valid, so drop ready as well.
340 # a "proper" ALU would have had to sync in the opcode and a/b ops
341 m.d.sync += self.p.ready_o.eq(0)
342
343 # ok so the counter's running: when it gets to 1, fire the output
344 with m.If((self.counter == 1) | go_now):
345 # set the output as valid if the recipient is ready for it
346 m.d.sync += self.n.valid_o.eq(1)
347 with m.If(self.n.ready_i & self.n.valid_o):
348 m.d.sync += self.n.valid_o.eq(0)
349 # recipient said it was ready: reset back to known-good.
350 m.d.sync += self.counter.eq(0) # reset the counter
351 m.d.sync += self.o.eq(0) # clear the output for tidiness sake
352
353 # countdown to 1 (transition from 1 to 0 only on acknowledgement)
354 with m.If(self.counter > 1):
355 m.d.sync += self.counter.eq(self.counter - 1)
356
357 return m
358
359 def __iter__(self):
360 yield self.op
361 yield self.a
362 yield self.b
363 yield self.o
364
365 def ports(self):
366 return list(self)
367
368 def run_op(dut, a, b, op, inv_a=0):
369 yield dut.a.eq(a)
370 yield dut.b.eq(b)
371 yield dut.op.insn_type.eq(op)
372 yield dut.op.invert_a.eq(inv_a)
373 yield dut.n.ready_i.eq(0)
374 yield dut.p.valid_i.eq(1)
375 yield
376 while True:
377 yield
378 vld = yield dut.n.valid_o
379 if vld:
380 break
381 yield
382
383 result = yield dut.o
384 yield dut.p.valid_i.eq(0)
385 yield dut.n.ready_i.eq(0)
386 yield
387
388 return result
389
390
391 def alu_sim(dut):
392 result = yield from run_op(dut, 5, 3, InternalOp.OP_ADD)
393 print ("alu_sim add", result)
394 assert (result == 8)
395
396 result = yield from run_op(dut, 2, 3, InternalOp.OP_MUL_L64)
397 print ("alu_sim mul", result)
398 assert (result == 6)
399
400 result = yield from run_op(dut, 5, 3, InternalOp.OP_ADD, inv_a=1)
401 print ("alu_sim add-inv", result)
402 assert (result == 65533)
403
404
405 def test_alu():
406 alu = ALU(width=16)
407 run_simulation(alu, {"sync": alu_sim(alu)}, vcd_name='test_alusim.vcd')
408
409 vl = rtlil.convert(alu, ports=alu.ports())
410 with open("test_alu.il", "w") as f:
411 f.write(vl)
412
413
414 if __name__ == "__main__":
415 test_alu()
416
417 # alu = BranchALU(width=16)
418 # vl = rtlil.convert(alu, ports=alu.ports())
419 # with open("test_branch_alu.il", "w") as f:
420 # f.write(vl)
421