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