1 """*Experimental* ALU: based on nmigen alu_hier.py, includes branch-compare ALU
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
8 A "real" integer ALU would place the answers onto the output bus after
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
18 from soc
.decoder
.power_enums
import InternalOp
, Function
, CryIn
20 from soc
.fu
.alu
.alu_input_record
import CompALUOpSubset
21 from soc
.fu
.cr
.cr_input_record
import CompCROpSubset
28 class Adder(Elaboratable
):
29 def __init__(self
, width
):
30 self
.invert_a
= Signal()
31 self
.a
= Signal(width
)
32 self
.b
= Signal(width
)
33 self
.o
= Signal(width
)
35 def elaborate(self
, platform
):
37 with m
.If(self
.invert_a
):
38 m
.d
.comb
+= self
.o
.eq((~self
.a
) + self
.b
)
40 m
.d
.comb
+= self
.o
.eq(self
.a
+ self
.b
)
44 class Subtractor(Elaboratable
):
45 def __init__(self
, width
):
46 self
.a
= Signal(width
)
47 self
.b
= Signal(width
)
48 self
.o
= Signal(width
)
50 def elaborate(self
, platform
):
52 m
.d
.comb
+= self
.o
.eq(self
.a
- self
.b
)
56 class Multiplier(Elaboratable
):
57 def __init__(self
, width
):
58 self
.a
= Signal(width
)
59 self
.b
= Signal(width
)
60 self
.o
= Signal(width
)
62 def elaborate(self
, platform
):
64 m
.d
.comb
+= self
.o
.eq(self
.a
* self
.b
)
68 class Shifter(Elaboratable
):
69 def __init__(self
, width
):
71 self
.a
= Signal(width
)
72 self
.b
= Signal(width
)
73 self
.o
= Signal(width
)
75 def elaborate(self
, platform
):
77 btrunc
= Signal(self
.width
)
78 m
.d
.comb
+= btrunc
.eq(self
.b
& Const((1<<self
.width
)-1))
79 m
.d
.comb
+= self
.o
.eq(self
.a
>> btrunc
)
85 class DummyALU(Elaboratable
):
86 def __init__(self
, width
):
87 self
.p
= Dummy() # make look like nmutil pipeline API
88 self
.p
.data_i
= Dummy()
89 self
.p
.data_i
.ctx
= Dummy()
90 self
.n
= Dummy() # make look like nmutil pipeline API
91 self
.n
.data_o
= Dummy()
92 self
.p
.valid_i
= Signal()
93 self
.p
.ready_o
= Signal()
94 self
.n
.ready_i
= Signal()
95 self
.n
.valid_o
= Signal()
96 self
.counter
= Signal(4)
97 self
.op
= CompCROpSubset()
99 i
.append(Signal(width
, name
="i1"))
100 i
.append(Signal(width
, name
="i2"))
102 self
.a
, self
.b
= i
[0], i
[1]
103 self
.out
= Array([Signal(width
)])
106 # more "look like nmutil pipeline API"
107 self
.p
.data_i
.ctx
.op
= self
.op
108 self
.p
.data_i
.a
= self
.a
109 self
.p
.data_i
.b
= self
.b
110 self
.n
.data_o
.o
= self
.o
112 def elaborate(self
, platform
):
115 go_now
= Signal(reset_less
=True) # testing no-delay ALU
117 with m
.If(self
.p
.valid_i
):
118 # input is valid. next check, if we already said "ready" or not
119 with m
.If(~self
.p
.ready_o
):
120 # we didn't say "ready" yet, so say so and initialise
121 m
.d
.sync
+= self
.p
.ready_o
.eq(1)
123 m
.d
.sync
+= self
.o
.eq(self
.a
)
124 m
.d
.comb
+= go_now
.eq(1)
125 m
.d
.sync
+= self
.counter
.eq(1)
128 # input says no longer valid, so drop ready as well.
129 # a "proper" ALU would have had to sync in the opcode and a/b ops
130 m
.d
.sync
+= self
.p
.ready_o
.eq(0)
132 # ok so the counter's running: when it gets to 1, fire the output
133 with m
.If((self
.counter
== 1) | go_now
):
134 # set the output as valid if the recipient is ready for it
135 m
.d
.sync
+= self
.n
.valid_o
.eq(1)
136 with m
.If(self
.n
.ready_i
& self
.n
.valid_o
):
137 m
.d
.sync
+= self
.n
.valid_o
.eq(0)
138 # recipient said it was ready: reset back to known-good.
139 m
.d
.sync
+= self
.counter
.eq(0) # reset the counter
140 m
.d
.sync
+= self
.o
.eq(0) # clear the output for tidiness sake
142 # countdown to 1 (transition from 1 to 0 only on acknowledgement)
143 with m
.If(self
.counter
> 1):
144 m
.d
.sync
+= self
.counter
.eq(self
.counter
- 1)
149 yield from self
.op
.ports()
158 class ALU(Elaboratable
):
159 def __init__(self
, width
):
160 self
.p
= Dummy() # make look like nmutil pipeline API
161 self
.p
.data_i
= Dummy()
162 self
.p
.data_i
.ctx
= Dummy()
163 self
.n
= Dummy() # make look like nmutil pipeline API
164 self
.n
.data_o
= Dummy()
165 self
.p
.valid_i
= Signal()
166 self
.p
.ready_o
= Signal()
167 self
.n
.ready_i
= Signal()
168 self
.n
.valid_o
= Signal()
169 self
.counter
= Signal(4)
170 self
.op
= CompALUOpSubset()
172 i
.append(Signal(width
, name
="i1"))
173 i
.append(Signal(width
, name
="i2"))
175 self
.a
, self
.b
= i
[0], i
[1]
176 self
.out
= Array([Signal(width
)])
179 # more "look like nmutil pipeline API"
180 self
.p
.data_i
.ctx
.op
= self
.op
181 self
.p
.data_i
.a
= self
.a
182 self
.p
.data_i
.b
= self
.b
183 self
.n
.data_o
.o
= self
.o
185 def elaborate(self
, platform
):
187 add
= Adder(self
.width
)
188 mul
= Multiplier(self
.width
)
189 shf
= Shifter(self
.width
)
191 m
.submodules
.add
= add
192 m
.submodules
.mul
= mul
193 m
.submodules
.shf
= shf
195 # really should not activate absolutely all ALU inputs like this
196 for mod
in [add
, mul
, shf
]:
202 # pass invert (and carry later)
203 m
.d
.comb
+= add
.invert_a
.eq(self
.op
.invert_a
)
205 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)
213 # as this is a "fake" pipeline, just grab the output right now
214 with m
.If(self
.op
.insn_type
== InternalOp
.OP_ADD
):
215 m
.d
.sync
+= self
.o
.eq(add
.o
)
216 with m
.Elif(self
.op
.insn_type
== InternalOp
.OP_MUL_L64
):
217 m
.d
.sync
+= self
.o
.eq(mul
.o
)
218 with m
.Elif(self
.op
.insn_type
== InternalOp
.OP_SHR
):
219 m
.d
.sync
+= self
.o
.eq(shf
.o
)
222 # NOTE: all of these are fake, just something to test
224 # MUL, to take 5 instructions
225 with m
.If(self
.op
.insn_type
== InternalOp
.OP_MUL_L64
):
226 m
.d
.sync
+= self
.counter
.eq(5)
228 with m
.Elif(self
.op
.insn_type
== InternalOp
.OP_SHR
):
229 m
.d
.sync
+= self
.counter
.eq(7)
230 # ADD/SUB to take 2, straight away
231 with m
.If(self
.op
.insn_type
== InternalOp
.OP_ADD
):
232 m
.d
.sync
+= self
.counter
.eq(3)
233 # others to take 1, straight away
235 m
.d
.comb
+= go_now
.eq(1)
236 m
.d
.sync
+= self
.counter
.eq(1)
239 # input says no longer valid, so drop ready as well.
240 # a "proper" ALU would have had to sync in the opcode and a/b ops
241 m
.d
.sync
+= self
.p
.ready_o
.eq(0)
243 # ok so the counter's running: when it gets to 1, fire the output
244 with m
.If((self
.counter
== 1) | go_now
):
245 # set the output as valid if the recipient is ready for it
246 m
.d
.sync
+= self
.n
.valid_o
.eq(1)
247 with m
.If(self
.n
.ready_i
& self
.n
.valid_o
):
248 m
.d
.sync
+= self
.n
.valid_o
.eq(0)
249 # recipient said it was ready: reset back to known-good.
250 m
.d
.sync
+= self
.counter
.eq(0) # reset the counter
251 m
.d
.sync
+= self
.o
.eq(0) # clear the output for tidiness sake
253 # countdown to 1 (transition from 1 to 0 only on acknowledgement)
254 with m
.If(self
.counter
> 1):
255 m
.d
.sync
+= self
.counter
.eq(self
.counter
- 1)
260 yield from self
.op
.ports()
269 class BranchOp(Elaboratable
):
270 def __init__(self
, width
, op
):
271 self
.a
= Signal(width
)
272 self
.b
= Signal(width
)
273 self
.o
= Signal(width
)
276 def elaborate(self
, platform
):
278 m
.d
.comb
+= self
.o
.eq(Mux(self
.op(self
.a
, self
.b
), 1, 0))
282 class BranchALU(Elaboratable
):
283 def __init__(self
, width
):
284 self
.p
.valid_i
= Signal()
285 self
.p
.ready_o
= Signal()
286 self
.n
.ready_i
= Signal()
287 self
.n
.valid_o
= Signal()
288 self
.counter
= Signal(4)
291 i
.append(Signal(width
, name
="i1"))
292 i
.append(Signal(width
, name
="i2"))
294 self
.a
, self
.b
= i
[0], i
[1]
295 self
.out
= Array([Signal(width
)])
299 def elaborate(self
, platform
):
301 bgt
= BranchOp(self
.width
, operator
.gt
)
302 blt
= BranchOp(self
.width
, operator
.lt
)
303 beq
= BranchOp(self
.width
, operator
.eq
)
304 bne
= BranchOp(self
.width
, operator
.ne
)
306 m
.submodules
.bgt
= bgt
307 m
.submodules
.blt
= blt
308 m
.submodules
.beq
= beq
309 m
.submodules
.bne
= bne
310 for mod
in [bgt
, blt
, beq
, bne
]:
316 go_now
= Signal(reset_less
=True) # testing no-delay ALU
317 with m
.If(self
.p
.valid_i
):
318 # input is valid. next check, if we already said "ready" or not
319 with m
.If(~self
.p
.ready_o
):
320 # we didn't say "ready" yet, so say so and initialise
321 m
.d
.sync
+= self
.p
.ready_o
.eq(1)
323 # as this is a "fake" pipeline, just grab the output right now
324 with m
.Switch(self
.op
):
325 for i
, mod
in enumerate([bgt
, blt
, beq
, bne
]):
327 m
.d
.sync
+= self
.o
.eq(mod
.o
)
328 m
.d
.sync
+= self
.counter
.eq(5) # branch to take 5 cycles (fake)
329 #m.d.comb += go_now.eq(1)
331 # input says no longer valid, so drop ready as well.
332 # a "proper" ALU would have had to sync in the opcode and a/b ops
333 m
.d
.sync
+= self
.p
.ready_o
.eq(0)
335 # ok so the counter's running: when it gets to 1, fire the output
336 with m
.If((self
.counter
== 1) | go_now
):
337 # set the output as valid if the recipient is ready for it
338 m
.d
.sync
+= self
.n
.valid_o
.eq(1)
339 with m
.If(self
.n
.ready_i
& self
.n
.valid_o
):
340 m
.d
.sync
+= self
.n
.valid_o
.eq(0)
341 # recipient said it was ready: reset back to known-good.
342 m
.d
.sync
+= self
.counter
.eq(0) # reset the counter
343 m
.d
.sync
+= self
.o
.eq(0) # clear the output for tidiness sake
345 # countdown to 1 (transition from 1 to 0 only on acknowledgement)
346 with m
.If(self
.counter
> 1):
347 m
.d
.sync
+= self
.counter
.eq(self
.counter
- 1)
360 def run_op(dut
, a
, b
, op
, inv_a
=0):
363 yield dut
.op
.insn_type
.eq(op
)
364 yield dut
.op
.invert_a
.eq(inv_a
)
365 yield dut
.n
.ready_i
.eq(0)
366 yield dut
.p
.valid_i
.eq(1)
370 vld
= yield dut
.n
.valid_o
376 yield dut
.p
.valid_i
.eq(0)
377 yield dut
.n
.ready_i
.eq(0)
384 result
= yield from run_op(dut
, 5, 3, InternalOp
.OP_ADD
)
385 print ("alu_sim add", result
)
388 result
= yield from run_op(dut
, 2, 3, InternalOp
.OP_MUL_L64
)
389 print ("alu_sim mul", result
)
392 result
= yield from run_op(dut
, 5, 3, InternalOp
.OP_ADD
, inv_a
=1)
393 print ("alu_sim add-inv", result
)
394 assert (result
== 65533)
399 run_simulation(alu
, {"sync": alu_sim(alu
)}, vcd_name
='test_alusim.vcd')
401 vl
= rtlil
.convert(alu
, ports
=alu
.ports())
402 with
open("test_alu.il", "w") as f
:
406 if __name__
== "__main__":
409 # alu = BranchALU(width=16)
410 # vl = rtlil.convert(alu, ports=alu.ports())
411 # with open("test_branch_alu.il", "w") as f: