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