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