convert BranchALU to temporary conformant API
[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 import operator
21
22
23 class CompALUOpSubset(Record):
24 """CompALUOpSubset
25
26 a copy of the relevant subset information from Decode2Execute1Type
27 needed for ALU operations. use with eq_from_execute1 (below) to
28 grab subsets.
29 """
30 def __init__(self, name=None):
31 layout = (('insn_type', InternalOp),
32 ('fn_unit', Function),
33 ('nia', 64),
34 ('imm_data', Layout((("imm", 64), ("imm_ok", 1)))),
35 #'cr = Signal(32, reset_less=True) # NO: this is from the CR SPR
36 #'xerc = XerBits() # NO: this is from the XER SPR
37 ('lk', 1),
38 ('rc', Layout((("rc", 1), ("rc_ok", 1)))),
39 ('oe', Layout((("oe", 1), ("oe_ok", 1)))),
40 ('invert_a', 1),
41 ('invert_out', 1),
42 ('input_carry', CryIn),
43 ('output_carry', 1),
44 ('input_cr', 1),
45 ('output_cr', 1),
46 ('is_32bit', 1),
47 ('is_signed', 1),
48 ('byte_reverse', 1),
49 ('sign_extend', 1))
50
51 Record.__init__(self, Layout(layout), name=name)
52
53 # grrr. Record does not have kwargs
54 self.insn_type.reset_less = True
55 self.fn_unit.reset_less = True
56 self.nia.reset_less = True
57 #self.cr = Signal(32, reset_less = True
58 #self.xerc = XerBits(
59 self.lk.reset_less = True
60 self.invert_a.reset_less = True
61 self.invert_out.reset_less = True
62 self.input_carry.reset_less = True
63 self.output_carry.reset_less = True
64 self.input_cr.reset_less = True
65 self.output_cr.reset_less = True
66 self.is_32bit.reset_less = True
67 self.is_signed.reset_less = True
68 self.byte_reverse.reset_less = True
69 self.sign_extend.reset_less = True
70
71 def eq_from_execute1(self, other):
72 """ use this to copy in from Decode2Execute1Type
73 """
74 res = []
75 for fname, sig in self.fields.items():
76 eqfrom = other.fields[fname]
77 res.append(sig.eq(eqfrom))
78 return res
79
80 def ports(self):
81 return [self.insn_type,
82 self.nia,
83 #self.cr,
84 #self.xerc,
85 self.lk,
86 self.invert_a,
87 self.invert_out,
88 self.input_carry,
89 self.output_carry,
90 self.input_cr,
91 self.output_cr,
92 self.is_32bit,
93 self.is_signed,
94 self.byte_reverse,
95 self.sign_extend,
96 ]
97
98
99 class Adder(Elaboratable):
100 def __init__(self, width):
101 self.invert_a = Signal()
102 self.a = Signal(width)
103 self.b = Signal(width)
104 self.o = Signal(width)
105
106 def elaborate(self, platform):
107 m = Module()
108 with m.If(self.invert_a):
109 m.d.comb += self.o.eq((~self.a) + self.b)
110 with m.Else():
111 m.d.comb += self.o.eq(self.a + self.b)
112 return m
113
114
115 class Subtractor(Elaboratable):
116 def __init__(self, width):
117 self.a = Signal(width)
118 self.b = Signal(width)
119 self.o = Signal(width)
120
121 def elaborate(self, platform):
122 m = Module()
123 m.d.comb += self.o.eq(self.a - self.b)
124 return m
125
126
127 class Multiplier(Elaboratable):
128 def __init__(self, width):
129 self.a = Signal(width)
130 self.b = Signal(width)
131 self.o = Signal(width)
132
133 def elaborate(self, platform):
134 m = Module()
135 m.d.comb += self.o.eq(self.a * self.b)
136 return m
137
138
139 class Shifter(Elaboratable):
140 def __init__(self, width):
141 self.width = width
142 self.a = Signal(width)
143 self.b = Signal(width)
144 self.o = Signal(width)
145
146 def elaborate(self, platform):
147 m = Module()
148 btrunc = Signal(self.width)
149 m.d.comb += btrunc.eq(self.b & Const((1<<self.width)-1))
150 m.d.comb += self.o.eq(self.a >> btrunc)
151 return m
152
153
154 class ALU(Elaboratable):
155 def __init__(self, width):
156 self.p_valid_i = Signal()
157 self.p_ready_o = Signal()
158 self.n_ready_i = Signal()
159 self.n_valid_o = Signal()
160 self.counter = Signal(4)
161 self.op = CompALUOpSubset()
162 i = []
163 i.append(Signal(width, name="i1"))
164 i.append(Signal(width, name="i2"))
165 self.i = Array(i)
166 self.a, self.b = i[0], i[1]
167 self.out = Array([Signal(width)])
168 self.o = self.out[0]
169 self.width = width
170
171 def elaborate(self, platform):
172 m = Module()
173 add = Adder(self.width)
174 mul = Multiplier(self.width)
175 shf = Shifter(self.width)
176
177 m.submodules.add = add
178 m.submodules.mul = mul
179 m.submodules.shf = shf
180
181 # really should not activate absolutely all ALU inputs like this
182 for mod in [add, mul, shf]:
183 m.d.comb += [
184 mod.a.eq(self.a),
185 mod.b.eq(self.b),
186 ]
187
188 # pass invert (and carry later)
189 m.d.comb += add.invert_a.eq(self.op.invert_a)
190
191 go_now = Signal(reset_less=True) # testing no-delay ALU
192
193 with m.If(self.p_valid_i):
194 # input is valid. next check, if we already said "ready" or not
195 with m.If(~self.p_ready_o):
196 # we didn't say "ready" yet, so say so and initialise
197 m.d.sync += self.p_ready_o.eq(1)
198
199 # as this is a "fake" pipeline, just grab the output right now
200 with m.If(self.op.insn_type == InternalOp.OP_ADD):
201 m.d.sync += self.o.eq(add.o)
202 with m.Elif(self.op.insn_type == InternalOp.OP_MUL_L64):
203 m.d.sync += self.o.eq(mul.o)
204 with m.Elif(self.op.insn_type == InternalOp.OP_SHR):
205 m.d.sync += self.o.eq(shf.o)
206 # TODO: SUB
207
208 # NOTE: all of these are fake, just something to test
209
210 # MUL, to take 5 instructions
211 with m.If(self.op.insn_type == InternalOp.OP_MUL_L64):
212 m.d.sync += self.counter.eq(5)
213 # SHIFT to take 7
214 with m.Elif(self.op.insn_type == InternalOp.OP_SHR):
215 m.d.sync += self.counter.eq(7)
216 # ADD/SUB to take 2, straight away
217 with m.If(self.op.insn_type == InternalOp.OP_ADD):
218 m.d.sync += self.counter.eq(3)
219 # others to take 1, straight away
220 with m.Else():
221 m.d.comb += go_now.eq(1)
222 m.d.sync += self.counter.eq(1)
223
224 with m.Else():
225 # input says no longer valid, so drop ready as well.
226 # a "proper" ALU would have had to sync in the opcode and a/b ops
227 m.d.sync += self.p_ready_o.eq(0)
228
229 # ok so the counter's running: when it gets to 1, fire the output
230 with m.If((self.counter == 1) | go_now):
231 # set the output as valid if the recipient is ready for it
232 m.d.sync += self.n_valid_o.eq(1)
233 with m.If(self.n_ready_i & self.n_valid_o):
234 m.d.sync += self.n_valid_o.eq(0)
235 # recipient said it was ready: reset back to known-good.
236 m.d.sync += self.counter.eq(0) # reset the counter
237 m.d.sync += self.o.eq(0) # clear the output for tidiness sake
238
239 # countdown to 1 (transition from 1 to 0 only on acknowledgement)
240 with m.If(self.counter > 1):
241 m.d.sync += self.counter.eq(self.counter - 1)
242
243 return m
244
245 def __iter__(self):
246 yield from self.op.ports()
247 yield self.a
248 yield self.b
249 yield self.o
250
251 def ports(self):
252 return list(self)
253
254
255 class BranchOp(Elaboratable):
256 def __init__(self, width, op):
257 self.a = Signal(width)
258 self.b = Signal(width)
259 self.o = Signal(width)
260 self.op = op
261
262 def elaborate(self, platform):
263 m = Module()
264 m.d.comb += self.o.eq(Mux(self.op(self.a, self.b), 1, 0))
265 return m
266
267
268 class BranchALU(Elaboratable):
269 def __init__(self, width):
270 self.p_valid_i = Signal()
271 self.p_ready_o = Signal()
272 self.n_ready_i = Signal()
273 self.n_valid_o = Signal()
274 self.counter = Signal(4)
275 self.op = Signal(2)
276 i = []
277 i.append(Signal(width, name="i1"))
278 i.append(Signal(width, name="i2"))
279 self.i = Array(i)
280 self.a, self.b = i[0], i[1]
281 self.out = Array([Signal(width)])
282 self.o = self.out[0]
283 self.width = width
284
285 def elaborate(self, platform):
286 m = Module()
287 bgt = BranchOp(self.width, operator.gt)
288 blt = BranchOp(self.width, operator.lt)
289 beq = BranchOp(self.width, operator.eq)
290 bne = BranchOp(self.width, operator.ne)
291
292 m.submodules.bgt = bgt
293 m.submodules.blt = blt
294 m.submodules.beq = beq
295 m.submodules.bne = bne
296 for mod in [bgt, blt, beq, bne]:
297 m.d.comb += [
298 mod.a.eq(self.a),
299 mod.b.eq(self.b),
300 ]
301
302 go_now = Signal(reset_less=True) # testing no-delay ALU
303 with m.If(self.p_valid_i):
304 # input is valid. next check, if we already said "ready" or not
305 with m.If(~self.p_ready_o):
306 # we didn't say "ready" yet, so say so and initialise
307 m.d.sync += self.p_ready_o.eq(1)
308
309 # as this is a "fake" pipeline, just grab the output right now
310 with m.Switch(self.op):
311 for i, mod in enumerate([bgt, blt, beq, bne]):
312 with m.Case(i):
313 m.d.sync += self.o.eq(mod.o)
314 m.d.sync += self.counter.eq(5) # branch to take 5 cycles (fake)
315 #m.d.comb += go_now.eq(1)
316 with m.Else():
317 # input says no longer valid, so drop ready as well.
318 # a "proper" ALU would have had to sync in the opcode and a/b ops
319 m.d.sync += self.p_ready_o.eq(0)
320
321 # ok so the counter's running: when it gets to 1, fire the output
322 with m.If((self.counter == 1) | go_now):
323 # set the output as valid if the recipient is ready for it
324 m.d.sync += self.n_valid_o.eq(1)
325 with m.If(self.n_ready_i & self.n_valid_o):
326 m.d.sync += self.n_valid_o.eq(0)
327 # recipient said it was ready: reset back to known-good.
328 m.d.sync += self.counter.eq(0) # reset the counter
329 m.d.sync += self.o.eq(0) # clear the output for tidiness sake
330
331 # countdown to 1 (transition from 1 to 0 only on acknowledgement)
332 with m.If(self.counter > 1):
333 m.d.sync += self.counter.eq(self.counter - 1)
334
335 return m
336
337 def __iter__(self):
338 yield self.op
339 yield self.a
340 yield self.b
341 yield self.o
342
343 def ports(self):
344 return list(self)
345
346 def run_op(dut, a, b, op, inv_a=0):
347 yield dut.a.eq(a)
348 yield dut.b.eq(b)
349 yield dut.op.insn_type.eq(op)
350 yield dut.op.invert_a.eq(inv_a)
351 yield dut.n_ready_i.eq(0)
352 yield dut.p_valid_i.eq(1)
353 yield
354 while True:
355 yield
356 n_valid_o = yield dut.n_valid_o
357 if n_valid_o:
358 break
359 yield
360
361 result = yield dut.o
362 yield dut.p_valid_i.eq(0)
363 yield dut.n_ready_i.eq(0)
364 yield
365
366 return result
367
368
369 def alu_sim(dut):
370 result = yield from run_op(dut, 5, 3, InternalOp.OP_ADD)
371 print ("alu_sim add", result)
372 assert (result == 8)
373
374 result = yield from run_op(dut, 2, 3, InternalOp.OP_MUL_L64)
375 print ("alu_sim mul", result)
376 assert (result == 6)
377
378 result = yield from run_op(dut, 5, 3, InternalOp.OP_ADD, inv_a=1)
379 print ("alu_sim add-inv", result)
380 assert (result == 65533)
381
382
383 def test_alu():
384 alu = ALU(width=16)
385 run_simulation(alu, alu_sim(alu), vcd_name='test_alusim.vcd')
386
387 vl = rtlil.convert(alu, ports=alu.ports())
388 with open("test_alu.il", "w") as f:
389 f.write(vl)
390
391
392 if __name__ == "__main__":
393 test_alu()
394
395 alu = BranchALU(width=16)
396 vl = rtlil.convert(alu, ports=alu.ports())
397 with open("test_branch_alu.il", "w") as f:
398 f.write(vl)
399