rename output signals in Test ALU
[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, name="add_o")
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, name="sub_o")
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, name="mul_o")
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, name="shf_o")
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
86 class DummyALU(Elaboratable):
87 def __init__(self, width):
88 self.p = Dummy() # make look like nmutil pipeline API
89 self.p.data_i = Dummy()
90 self.p.data_i.ctx = Dummy()
91 self.n = Dummy() # make look like nmutil pipeline API
92 self.n.data_o = Dummy()
93 self.p.valid_i = Signal()
94 self.p.ready_o = Signal()
95 self.n.ready_i = Signal()
96 self.n.valid_o = Signal()
97 self.counter = Signal(4)
98 self.op = CompCROpSubset()
99 i = []
100 i.append(Signal(width, name="i1"))
101 i.append(Signal(width, name="i2"))
102 i.append(Signal(width, name="i3"))
103 self.i = Array(i)
104 self.a, self.b, self.c = i[0], i[1], i[2]
105 self.out = Array([Signal(width, name="alu_o")])
106 self.o = self.out[0]
107 self.width = width
108 # more "look like nmutil pipeline API"
109 self.p.data_i.ctx.op = self.op
110 self.p.data_i.a = self.a
111 self.p.data_i.b = self.b
112 self.p.data_i.c = self.c
113 self.n.data_o.o = self.o
114
115 def elaborate(self, platform):
116 m = Module()
117
118 go_now = Signal(reset_less=True) # testing no-delay ALU
119
120 with m.If(self.p.valid_i):
121 # input is valid. next check, if we already said "ready" or not
122 with m.If(~self.p.ready_o):
123 # we didn't say "ready" yet, so say so and initialise
124 m.d.sync += self.p.ready_o.eq(1)
125
126 m.d.sync += self.o.eq(self.a)
127 m.d.comb += go_now.eq(1)
128 m.d.sync += self.counter.eq(1)
129
130 with m.Else():
131 # input says no longer valid, so drop ready as well.
132 # a "proper" ALU would have had to sync in the opcode and a/b ops
133 m.d.sync += self.p.ready_o.eq(0)
134
135 # ok so the counter's running: when it gets to 1, fire the output
136 with m.If((self.counter == 1) | go_now):
137 # set the output as valid if the recipient is ready for it
138 m.d.sync += self.n.valid_o.eq(1)
139 with m.If(self.n.ready_i & self.n.valid_o):
140 m.d.sync += self.n.valid_o.eq(0)
141 # recipient said it was ready: reset back to known-good.
142 m.d.sync += self.counter.eq(0) # reset the counter
143 m.d.sync += self.o.eq(0) # clear the output for tidiness sake
144
145 # countdown to 1 (transition from 1 to 0 only on acknowledgement)
146 with m.If(self.counter > 1):
147 m.d.sync += self.counter.eq(self.counter - 1)
148
149 return m
150
151 def __iter__(self):
152 yield from self.op.ports()
153 yield self.a
154 yield self.b
155 yield self.c
156 yield self.o
157
158 def ports(self):
159 return list(self)
160
161
162 class ALU(Elaboratable):
163 def __init__(self, width):
164 self.p = Dummy() # make look like nmutil pipeline API
165 self.p.data_i = Dummy()
166 self.p.data_i.ctx = Dummy()
167 self.n = Dummy() # make look like nmutil pipeline API
168 self.n.data_o = Dummy()
169 self.p.valid_i = Signal()
170 self.p.ready_o = Signal()
171 self.n.ready_i = Signal()
172 self.n.valid_o = Signal()
173 self.counter = Signal(4)
174 self.op = CompALUOpSubset()
175 i = []
176 i.append(Signal(width, name="i1"))
177 i.append(Signal(width, name="i2"))
178 self.i = Array(i)
179 self.a, self.b = i[0], i[1]
180 self.out = Array([Signal(width, name="alu_o")])
181 self.o = self.out[0]
182 self.width = width
183 # more "look like nmutil pipeline API"
184 self.p.data_i.ctx.op = self.op
185 self.p.data_i.a = self.a
186 self.p.data_i.b = self.b
187 self.n.data_o.o = self.o
188
189 def elaborate(self, platform):
190 m = Module()
191 add = Adder(self.width)
192 mul = Multiplier(self.width)
193 shf = Shifter(self.width)
194
195 m.submodules.add = add
196 m.submodules.mul = mul
197 m.submodules.shf = shf
198
199 # really should not activate absolutely all ALU inputs like this
200 for mod in [add, mul, shf]:
201 m.d.comb += [
202 mod.a.eq(self.a),
203 mod.b.eq(self.b),
204 ]
205
206 # pass invert (and carry later)
207 m.d.comb += add.invert_a.eq(self.op.invert_a)
208
209 go_now = Signal(reset_less=True) # testing no-delay ALU
210
211 with m.If(self.p.valid_i):
212 # input is valid. next check, if we already said "ready" or not
213 with m.If(~self.p.ready_o):
214 # we didn't say "ready" yet, so say so and initialise
215 m.d.sync += self.p.ready_o.eq(1)
216
217 # as this is a "fake" pipeline, just grab the output right now
218 with m.If(self.op.insn_type == InternalOp.OP_ADD):
219 m.d.sync += self.o.eq(add.o)
220 with m.Elif(self.op.insn_type == InternalOp.OP_MUL_L64):
221 m.d.sync += self.o.eq(mul.o)
222 with m.Elif(self.op.insn_type == InternalOp.OP_SHR):
223 m.d.sync += self.o.eq(shf.o)
224 # TODO: SUB
225
226 # NOTE: all of these are fake, just something to test
227
228 # MUL, to take 5 instructions
229 with m.If(self.op.insn_type == InternalOp.OP_MUL_L64):
230 m.d.sync += self.counter.eq(5)
231 # SHIFT to take 7
232 with m.Elif(self.op.insn_type == InternalOp.OP_SHR):
233 m.d.sync += self.counter.eq(7)
234 # ADD/SUB to take 2, straight away
235 with m.If(self.op.insn_type == InternalOp.OP_ADD):
236 m.d.sync += self.counter.eq(3)
237 # others to take 1, straight away
238 with m.Else():
239 m.d.comb += go_now.eq(1)
240 m.d.sync += self.counter.eq(1)
241
242 with m.Else():
243 # input says no longer valid, so drop ready as well.
244 # a "proper" ALU would have had to sync in the opcode and a/b ops
245 m.d.sync += self.p.ready_o.eq(0)
246
247 # ok so the counter's running: when it gets to 1, fire the output
248 with m.If((self.counter == 1) | go_now):
249 # set the output as valid if the recipient is ready for it
250 m.d.sync += self.n.valid_o.eq(1)
251 with m.If(self.n.ready_i & self.n.valid_o):
252 m.d.sync += self.n.valid_o.eq(0)
253 # recipient said it was ready: reset back to known-good.
254 m.d.sync += self.counter.eq(0) # reset the counter
255 m.d.sync += self.o.eq(0) # clear the output for tidiness sake
256
257 # countdown to 1 (transition from 1 to 0 only on acknowledgement)
258 with m.If(self.counter > 1):
259 m.d.sync += self.counter.eq(self.counter - 1)
260
261 return m
262
263 def __iter__(self):
264 yield from self.op.ports()
265 yield self.a
266 yield self.b
267 yield self.o
268
269 def ports(self):
270 return list(self)
271
272
273 class BranchOp(Elaboratable):
274 def __init__(self, width, op):
275 self.a = Signal(width)
276 self.b = Signal(width)
277 self.o = Signal(width)
278 self.op = op
279
280 def elaborate(self, platform):
281 m = Module()
282 m.d.comb += self.o.eq(Mux(self.op(self.a, self.b), 1, 0))
283 return m
284
285
286 class BranchALU(Elaboratable):
287 def __init__(self, width):
288 self.p = Dummy() # make look like nmutil pipeline API
289 self.p.data_i = Dummy()
290 self.p.data_i.ctx = Dummy()
291 self.n = Dummy() # make look like nmutil pipeline API
292 self.n.data_o = Dummy()
293 self.p.valid_i = Signal()
294 self.p.ready_o = Signal()
295 self.n.ready_i = Signal()
296 self.n.valid_o = Signal()
297 self.counter = Signal(4)
298 self.op = Signal(2)
299 i = []
300 i.append(Signal(width, name="i1"))
301 i.append(Signal(width, name="i2"))
302 self.i = Array(i)
303 self.a, self.b = i[0], i[1]
304 self.out = Array([Signal(width)])
305 self.o = self.out[0]
306 self.width = width
307
308 def elaborate(self, platform):
309 m = Module()
310 bgt = BranchOp(self.width, operator.gt)
311 blt = BranchOp(self.width, operator.lt)
312 beq = BranchOp(self.width, operator.eq)
313 bne = BranchOp(self.width, operator.ne)
314
315 m.submodules.bgt = bgt
316 m.submodules.blt = blt
317 m.submodules.beq = beq
318 m.submodules.bne = bne
319 for mod in [bgt, blt, beq, bne]:
320 m.d.comb += [
321 mod.a.eq(self.a),
322 mod.b.eq(self.b),
323 ]
324
325 go_now = Signal(reset_less=True) # testing no-delay ALU
326 with m.If(self.p.valid_i):
327 # input is valid. next check, if we already said "ready" or not
328 with m.If(~self.p.ready_o):
329 # we didn't say "ready" yet, so say so and initialise
330 m.d.sync += self.p.ready_o.eq(1)
331
332 # as this is a "fake" pipeline, just grab the output right now
333 with m.Switch(self.op):
334 for i, mod in enumerate([bgt, blt, beq, bne]):
335 with m.Case(i):
336 m.d.sync += self.o.eq(mod.o)
337 m.d.sync += self.counter.eq(5) # branch to take 5 cycles (fake)
338 #m.d.comb += go_now.eq(1)
339 with m.Else():
340 # input says no longer valid, so drop ready as well.
341 # a "proper" ALU would have had to sync in the opcode and a/b ops
342 m.d.sync += self.p.ready_o.eq(0)
343
344 # ok so the counter's running: when it gets to 1, fire the output
345 with m.If((self.counter == 1) | go_now):
346 # set the output as valid if the recipient is ready for it
347 m.d.sync += self.n.valid_o.eq(1)
348 with m.If(self.n.ready_i & self.n.valid_o):
349 m.d.sync += self.n.valid_o.eq(0)
350 # recipient said it was ready: reset back to known-good.
351 m.d.sync += self.counter.eq(0) # reset the counter
352 m.d.sync += self.o.eq(0) # clear the output for tidiness sake
353
354 # countdown to 1 (transition from 1 to 0 only on acknowledgement)
355 with m.If(self.counter > 1):
356 m.d.sync += self.counter.eq(self.counter - 1)
357
358 return m
359
360 def __iter__(self):
361 yield self.op
362 yield self.a
363 yield self.b
364 yield self.o
365
366 def ports(self):
367 return list(self)
368
369 def run_op(dut, a, b, op, inv_a=0):
370 yield dut.a.eq(a)
371 yield dut.b.eq(b)
372 yield dut.op.insn_type.eq(op)
373 yield dut.op.invert_a.eq(inv_a)
374 yield dut.n.ready_i.eq(0)
375 yield dut.p.valid_i.eq(1)
376 yield
377 while True:
378 yield
379 vld = yield dut.n.valid_o
380 if vld:
381 break
382 yield
383
384 result = yield dut.o
385 yield dut.p.valid_i.eq(0)
386 yield dut.n.ready_i.eq(0)
387 yield
388
389 return result
390
391
392 def alu_sim(dut):
393 result = yield from run_op(dut, 5, 3, InternalOp.OP_ADD)
394 print ("alu_sim add", result)
395 assert (result == 8)
396
397 result = yield from run_op(dut, 2, 3, InternalOp.OP_MUL_L64)
398 print ("alu_sim mul", result)
399 assert (result == 6)
400
401 result = yield from run_op(dut, 5, 3, InternalOp.OP_ADD, inv_a=1)
402 print ("alu_sim add-inv", result)
403 assert (result == 65533)
404
405
406 def test_alu():
407 alu = ALU(width=16)
408 run_simulation(alu, {"sync": alu_sim(alu)}, vcd_name='test_alusim.vcd')
409
410 vl = rtlil.convert(alu, ports=alu.ports())
411 with open("test_alu.il", "w") as f:
412 f.write(vl)
413
414
415 if __name__ == "__main__":
416 test_alu()
417
418 # alu = BranchALU(width=16)
419 # vl = rtlil.convert(alu, ports=alu.ports())
420 # with open("test_branch_alu.il", "w") as f:
421 # f.write(vl)
422