use power decoder InternalOp
[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
13 from nmigen.cli import main
14 from nmigen.cli import verilog, rtlil
15 from soc.decoder.power_enums import InternalOp
16
17 import operator
18
19
20 class Adder(Elaboratable):
21 def __init__(self, width):
22 self.a = Signal(width)
23 self.b = Signal(width)
24 self.o = Signal(width)
25
26 def elaborate(self, platform):
27 m = Module()
28 m.d.comb += self.o.eq(self.a + self.b)
29 return m
30
31
32 class Subtractor(Elaboratable):
33 def __init__(self, width):
34 self.a = Signal(width)
35 self.b = Signal(width)
36 self.o = Signal(width)
37
38 def elaborate(self, platform):
39 m = Module()
40 m.d.comb += self.o.eq(self.a - self.b)
41 return m
42
43
44 class Multiplier(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 Shifter(Elaboratable):
57 def __init__(self, width):
58 self.width = width
59 self.a = Signal(width)
60 self.b = Signal(width)
61 self.o = Signal(width)
62
63 def elaborate(self, platform):
64 m = Module()
65 btrunc = Signal(self.width)
66 m.d.comb += btrunc.eq(self.b & Const((1<<self.width)-1))
67 m.d.comb += self.o.eq(self.a >> btrunc)
68 return m
69
70
71 class ALU(Elaboratable):
72 def __init__(self, width):
73 self.p_valid_i = Signal()
74 self.p_ready_o = Signal()
75 self.n_ready_i = Signal()
76 self.n_valid_o = Signal()
77 self.counter = Signal(4)
78 self.op = Signal(InternalOp)
79 self.a = Signal(width)
80 self.b = Signal(width)
81 self.o = Signal(width)
82 self.width = width
83
84 def elaborate(self, platform):
85 m = Module()
86 add = Adder(self.width)
87 sub = Subtractor(self.width)
88 mul = Multiplier(self.width)
89 shf = Shifter(self.width)
90
91 m.submodules.add = add
92 m.submodules.sub = sub
93 m.submodules.mul = mul
94 m.submodules.shf = shf
95
96 # really should not activate absolutely all ALU inputs like this
97 for mod in [add, sub, mul, shf]:
98 m.d.comb += [
99 mod.a.eq(self.a),
100 mod.b.eq(self.b),
101 ]
102 go_now = Signal(reset_less=True) # testing no-delay ALU
103
104 with m.If(self.p_valid_i):
105 # input is valid. next check, if we already said "ready" or not
106 with m.If(~self.p_ready_o):
107 # we didn't say "ready" yet, so say so and initialise
108 m.d.sync += self.p_ready_o.eq(1)
109
110 # as this is a "fake" pipeline, just grab the output right now
111 with m.If(self.op == InternalOp.OP_ADD):
112 m.d.sync += self.o.eq(add.o)
113 with m.Elif(self.op == InternalOp.OP_MUL_L64):
114 m.d.sync += self.o.eq(mul.o)
115 with m.Elif(self.op == InternalOp.OP_SHR):
116 m.d.sync += self.o.eq(shf.o)
117 # TODO: SUB
118
119 with m.Switch(self.op):
120 for i, mod in enumerate([add, sub, mul, shf]):
121 with m.Case(i):
122 m.d.sync += self.o.eq(mod.o)
123 with m.If(self.op == 2): # MUL, to take 5 instructions
124 m.d.sync += self.counter.eq(5)
125 with m.Elif(self.op == 3): # SHIFT to take 7
126 m.d.sync += self.counter.eq(7)
127 with m.Elif(self.op == 1): # SUB to take 1, straight away
128 m.d.sync += self.counter.eq(1)
129 m.d.comb += go_now.eq(1)
130 with m.Else(): # ADD to take 2
131 m.d.sync += self.counter.eq(2)
132 with m.Else():
133 # input says no longer valid, so drop ready as well.
134 # a "proper" ALU would have had to sync in the opcode and a/b ops
135 m.d.sync += self.p_ready_o.eq(0)
136
137 # ok so the counter's running: when it gets to 1, fire the output
138 with m.If((self.counter == 1) | go_now):
139 # set the output as valid if the recipient is ready for it
140 m.d.sync += self.n_valid_o.eq(1)
141 with m.If(self.n_ready_i & self.n_valid_o):
142 m.d.sync += self.n_valid_o.eq(0)
143 # recipient said it was ready: reset back to known-good.
144 m.d.sync += self.counter.eq(0) # reset the counter
145 m.d.sync += self.o.eq(0) # clear the output for tidiness sake
146
147 # countdown to 1 (transition from 1 to 0 only on acknowledgement)
148 with m.If(self.counter > 1):
149 m.d.sync += self.counter.eq(self.counter - 1)
150
151 return m
152
153 def __iter__(self):
154 yield self.op
155 yield self.a
156 yield self.b
157 yield self.o
158
159 def ports(self):
160 return list(self)
161
162
163 class BranchOp(Elaboratable):
164 def __init__(self, width, op):
165 self.a = Signal(width)
166 self.b = Signal(width)
167 self.o = Signal(width)
168 self.op = op
169
170 def elaborate(self, platform):
171 m = Module()
172 m.d.comb += self.o.eq(Mux(self.op(self.a, self.b), 1, 0))
173 return m
174
175
176 class BranchALU(Elaboratable):
177 def __init__(self, width):
178 self.p_valid_i = Signal()
179 self.p_ready_o = Signal()
180 self.n_ready_i = Signal()
181 self.n_valid_o = Signal()
182 self.counter = Signal(4)
183 self.op = Signal(2)
184 self.a = Signal(width)
185 self.b = Signal(width)
186 self.o = Signal(width)
187 self.width = width
188
189 def elaborate(self, platform):
190 m = Module()
191 bgt = BranchOp(self.width, operator.gt)
192 blt = BranchOp(self.width, operator.lt)
193 beq = BranchOp(self.width, operator.eq)
194 bne = BranchOp(self.width, operator.ne)
195
196 m.submodules.bgt = bgt
197 m.submodules.blt = blt
198 m.submodules.beq = beq
199 m.submodules.bne = bne
200 for mod in [bgt, blt, beq, bne]:
201 m.d.comb += [
202 mod.a.eq(self.a),
203 mod.b.eq(self.b),
204 ]
205
206 go_now = Signal(reset_less=True) # testing no-delay ALU
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.Switch(self.op):
215 for i, mod in enumerate([bgt, blt, beq, bne]):
216 with m.Case(i):
217 m.d.sync += self.o.eq(mod.o)
218 m.d.sync += self.counter.eq(5) # branch to take 5 cycles (fake)
219 #m.d.comb += go_now.eq(1)
220 with m.Else():
221 # input says no longer valid, so drop ready as well.
222 # a "proper" ALU would have had to sync in the opcode and a/b ops
223 m.d.sync += self.p_ready_o.eq(0)
224
225 # ok so the counter's running: when it gets to 1, fire the output
226 with m.If((self.counter == 1) | go_now):
227 # set the output as valid if the recipient is ready for it
228 m.d.sync += self.n_valid_o.eq(1)
229 with m.If(self.n_ready_i & self.n_valid_o):
230 m.d.sync += self.n_valid_o.eq(0)
231 # recipient said it was ready: reset back to known-good.
232 m.d.sync += self.counter.eq(0) # reset the counter
233 m.d.sync += self.o.eq(0) # clear the output for tidiness sake
234
235 # countdown to 1 (transition from 1 to 0 only on acknowledgement)
236 with m.If(self.counter > 1):
237 m.d.sync += self.counter.eq(self.counter - 1)
238
239 return m
240
241 def __iter__(self):
242 yield self.op
243 yield self.a
244 yield self.b
245 yield self.o
246
247 def ports(self):
248 return list(self)
249
250
251 if __name__ == "__main__":
252 alu = ALU(width=16)
253 vl = rtlil.convert(alu, ports=alu.ports())
254 with open("test_alu.il", "w") as f:
255 f.write(vl)
256
257 alu = BranchALU(width=16)
258 vl = rtlil.convert(alu, ports=alu.ports())
259 with open("test_branch_alu.il", "w") as f:
260 f.write(vl)
261