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