experimenting with ld/st comp unit
[soc.git] / src / soc / experiment / compldst.py
1 """ LOAD / STORE Computation Unit. Also capable of doing ADD and ADD immediate
2
3 This module runs a "revolving door" set of four latches, based on
4 * Issue
5 * Go_Read
6 * Go_Addr
7 * Go_Write *OR* Go_Store
8
9 (Note that opc_l has been inverted (and qn used), due to SRLatch
10 default reset state being "0" rather than "1")
11
12 Also note: the LD/ST Comp Unit can act as a *standard ALU* doing
13 add and subtract.
14
15 Stores are activated when Go_Store is enabled, and uses the ALU
16 to add the immediate (imm_i) to the address (src1_i), and then
17 when ready (go_st_i and the ALU ready) the operand (src2_i) is stored
18 in the computed address.
19 """
20
21 from nmigen.compat.sim import run_simulation
22 from nmigen.cli import verilog, rtlil
23 from nmigen import Module, Signal, Mux, Cat, Elaboratable, Array
24
25 from nmutil.latch import SRLatch, latchregister
26
27 from soc.experiment.compalu_multi import go_record
28 from soc.experiment.testmem import TestMemory
29 from soc.decoder.power_enums import InternalOp
30
31 from soc.experiment.alu_hier import CompALUOpSubset
32
33
34 class LDSTCompUnit(Elaboratable):
35 """ LOAD / STORE / ADD / SUB Computation Unit
36
37 Inputs
38 ------
39
40 * :rwid: register width
41 * :alu: an ALU module
42 * :mem: a Memory Module (read-write capable)
43
44 Control Signals (In)
45 --------------------
46
47 * :oper_i: operation being carried out (LDST_OP_ADD, LDST_OP_LD)
48 * :issue_i: LD/ST is being "issued".
49 * :isalu_i: ADD/SUB is being "issued" (aka issue_alu_i)
50 * :shadown_i: Inverted-shadow is being held (stops STORE *and* WRITE)
51 * :go_rd_i: read is being actioned (latches in src regs)
52 * :go_wr_i: write mode (exactly like ALU CompUnit)
53 * :go_ad_i: address is being actioned (triggers actual mem LD)
54 * :go_st_i: store is being actioned (triggers actual mem STORE)
55 * :go_die_i: resets the unit back to "wait for issue"
56
57 Control Signals (Out)
58 ---------------------
59
60 * :busy_o: function unit is busy
61 * :rd_rel_o: request src1/src2
62 * :adr_rel_o: request address (from mem)
63 * :sto_rel_o: request store (to mem)
64 * :req_rel_o: request write (result)
65 * :load_mem_o: activate memory LOAD
66 * :stwd_mem_o: activate memory STORE
67
68 Note: load_mem_o, stwd_mem_o and req_rel_o MUST all be acknowledged
69 in a single cycle and the CompUnit set back to doing another op.
70 This means deasserting go_st_i, go_ad_i or go_wr_i as appropriate
71 depending on whether the operation is a STORE, LD, or a straight
72 ALU operation respectively.
73
74 Control Data (out)
75 ------------------
76 * :data_o: Dest out (LD or ALU)
77 * :addr_o: Address out (LD or ST)
78 """
79
80 def __init__(self, rwid, alu, mem, n_src=2, n_dst=1, debugtest=False):
81 self.rwid = rwid
82 self.alu = alu
83 self.mem = mem
84 self.debugtest = debugtest
85
86 self.counter = Signal(4)
87 src = []
88 for i in range(n_src):
89 j = i + 1 # name numbering to match src1/src2
90 src.append(Signal(rwid, name="src%d_i" % j, reset_less=True))
91
92 dst = []
93 for i in range(n_dst):
94 j = i + 1 # name numbering to match dest1/2...
95 dst.append(Signal(rwid, name="dest%d_i" % j, reset_less=True))
96
97 self.rd = go_record(n_src, name="rd") # read in, req out
98 self.wr = go_record(n_dst, name="wr") # write in, req out
99 self.go_rd_i = self.rd.go # temporary naming
100 self.go_wr_i = self.wr.go # temporary naming
101 self.rd_rel_o = self.rd.rel # temporary naming
102 self.req_rel_o = self.wr.rel # temporary naming
103
104 self.go_ad_i = Signal(reset_less=True) # go address in
105 self.go_st_i = Signal(reset_less=True) # go store in
106 self.issue_i = Signal(reset_less=True) # fn issue in
107 self.isalu_i = Signal(reset_less=True) # fn issue as ALU in
108 self.shadown_i = Signal(reset=1) # shadow function, defaults to ON
109 self.go_die_i = Signal() # go die (reset)
110
111 # operation / data input
112 self.oper_i = CompALUOpSubset() # operand
113 self.src_i = Array(src)
114 self.src1_i = src[0] # oper1 in
115 self.src2_i = src[1] # oper2 in
116
117 self.busy_o = Signal(reset_less=True) # fn busy out
118 self.dest = Array(dst)
119 self.data_o = dst[0] # Dest out
120 self.adr_rel_o = Signal(reset_less=True) # request address (from mem)
121 self.sto_rel_o = Signal(reset_less=True) # request store (to mem)
122 self.done_o = Signal(reset_less=True) # final release signal
123 self.data_o = Signal(rwid, reset_less=True) # Dest out (LD or ALU)
124 self.addr_o = Signal(rwid, reset_less=True) # Address out (LD or ST)
125
126 # hmm... TODO... move these to outside of LDSTCompUnit?
127 self.load_mem_o = Signal(reset_less=True) # activate memory LOAD
128 self.stwd_mem_o = Signal(reset_less=True) # activate memory STORE
129 self.ld_o = Signal(reset_less=True) # operation is a LD
130 self.st_o = Signal(reset_less=True) # operation is a ST
131
132 def elaborate(self, platform):
133 m = Module()
134 comb = m.d.comb
135 sync = m.d.sync
136
137 m.submodules.alu = self.alu
138 #m.submodules.mem = self.mem
139 m.submodules.src_l = src_l = SRLatch(sync=False, name="src")
140 m.submodules.opc_l = opc_l = SRLatch(sync=False, name="opc")
141 m.submodules.adr_l = adr_l = SRLatch(sync=False, name="adr")
142 m.submodules.req_l = req_l = SRLatch(sync=False, name="req")
143 m.submodules.sto_l = sto_l = SRLatch(sync=False, name="sto")
144
145 # shadow/go_die
146 reset_b = Signal(reset_less=True)
147 reset_w = Signal(reset_less=True)
148 reset_a = Signal(reset_less=True)
149 reset_s = Signal(reset_less=True)
150 reset_r = Signal(reset_less=True)
151 comb += reset_b.eq(self.go_st_i | self.wr.go |
152 self.go_ad_i | self.go_die_i)
153 comb += reset_w.eq(self.wr.go | self.go_die_i)
154 comb += reset_s.eq(self.go_st_i | self.go_die_i)
155 comb += reset_r.eq(self.rd.go | self.go_die_i)
156 # this one is slightly different, issue_alu_i selects wr.go)
157 a_sel = Mux(self.isalu_i, self.wr.go, self.go_ad_i)
158 comb += reset_a.eq(a_sel | self.go_die_i)
159
160 # opcode decode
161 op_alu = Signal(reset_less=True)
162 op_is_ld = Signal(reset_less=True)
163 op_is_st = Signal(reset_less=True)
164 op_ldst = Signal(reset_less=True)
165 op_is_imm = Signal(reset_less=True)
166
167 # ALU/LD data output control
168 alulatch = Signal(reset_less=True)
169 ldlatch = Signal(reset_less=True)
170
171 # src2 register
172 src2_r = Signal(self.rwid, reset_less=True)
173
174 # select immediate or src2 reg to add
175 src2_or_imm = Signal(self.rwid, reset_less=True)
176 src_sel = Signal(reset_less=True)
177
178 # issue can be either issue_i or issue_alu_i (isalu_i)
179 issue_i = Signal(reset_less=True)
180 comb += issue_i.eq(self.issue_i | self.isalu_i)
181
182 # Ripple-down the latches, each one set cancels the previous.
183 # NOTE: use sync to stop combinatorial loops.
184
185 # opcode latch - inverted so that busy resets to 0
186 sync += opc_l.s.eq(issue_i) # XXX NOTE: INVERTED FROM book!
187 sync += opc_l.r.eq(reset_b) # XXX NOTE: INVERTED FROM book!
188
189 # src operand latch
190 sync += src_l.s.eq(issue_i)
191 sync += src_l.r.eq(reset_r)
192
193 # addr latch
194 sync += adr_l.s.eq(self.rd.go)
195 sync += adr_l.r.eq(reset_a)
196
197 # dest operand latch
198 sync += req_l.s.eq(self.go_ad_i | self.go_st_i | self.wr.go)
199 sync += req_l.r.eq(reset_w)
200
201 # store latch
202 sync += sto_l.s.eq(self.rd.go) # XXX not sure which
203 sync += sto_l.r.eq(reset_s)
204
205 # create a latch/register for the operand
206 oper_r = CompALUOpSubset() # Dest register
207 latchregister(m, self.oper_i, oper_r, self.issue_i, name="oper_r")
208
209 # and one for the output from the ALU
210 data_r = Signal(self.rwid, reset_less=True) # Dest register
211 latchregister(m, self.alu.o, data_r, alulatch, "aluo_r")
212
213 # and pass the operation to the ALU
214 comb += self.alu.op.eq(oper_r)
215 comb += self.alu.op.insn_type.eq(InternalOp.OP_ADD) # override insn_type
216
217 # outputs: busy and release signals
218 busy_o = self.busy_o
219 comb += self.busy_o.eq(opc_l.q) # busy out
220 comb += self.rd.rel.eq(src_l.q & busy_o) # src1/src2 req rel
221 comb += self.sto_rel_o.eq(sto_l.q & busy_o & self.shadown_i & op_is_st)
222
223 # request release enabled based on if op is a LD/ST or a plain ALU
224 # if op is an ADD/SUB or a LD, req_rel activates.
225 wr_q = Signal(reset_less=True)
226 comb += wr_q.eq(req_l.q & (~op_ldst | op_is_ld))
227
228 comb += alulatch.eq((op_ldst & self.adr_rel_o) |
229 (~op_ldst & self.wr.rel))
230
231 # select immediate if opcode says so. however also change the latch
232 # to trigger *from* the opcode latch instead.
233 comb += src_sel.eq(Mux(op_is_imm, opc_l.qn, src_l.q))
234 comb += src2_or_imm.eq(Mux(op_is_imm, oper_r.imm_data.imm,
235 self.src2_i))
236
237 # create a latch/register for src1/src2 (include immediate select)
238 latchregister(m, self.src1_i, self.alu.a, src_l.q, name="src1_r")
239 latchregister(m, self.src2_i, src2_r, src_l.q, name="src2_r")
240 latchregister(m, src2_or_imm, self.alu.b, src_sel, name="imm_r")
241
242 # decode bits of operand (latched)
243 comb += op_is_imm.eq(oper_r.imm_data.imm_ok)
244 comb += op_is_st.eq(oper_r.insn_type == InternalOp.OP_STORE) # ST
245 comb += op_is_ld.eq(oper_r.insn_type == InternalOp.OP_LOAD) # LD
246 comb += op_ldst.eq(op_is_ld | op_is_st)
247 comb += self.load_mem_o.eq(op_is_ld & self.go_ad_i)
248 comb += self.stwd_mem_o.eq(op_is_st & self.go_st_i)
249 comb += self.ld_o.eq(op_is_ld)
250 comb += self.st_o.eq(op_is_st)
251
252 # on a go_read, tell the ALU we're accepting data.
253 # NOTE: this spells TROUBLE if the ALU isn't ready!
254 # go_read is only valid for one clock!
255 with m.If(self.rd.go): # src operands ready, GO!
256 with m.If(~self.alu.p_ready_o): # no ACK yet
257 m.d.comb += self.alu.p_valid_i.eq(1) # so indicate valid
258
259 # only proceed if ALU says its output is valid
260 with m.If(self.alu.n_valid_o):
261 # write req release out. waits until shadow is dropped.
262 comb += self.wr.rel.eq(wr_q & busy_o & self.shadown_i)
263 # address release only happens on LD/ST, and is shadowed.
264 comb += self.adr_rel_o.eq(adr_l.q & op_ldst & busy_o &
265 self.shadown_i)
266 # when output latch is ready, and ALU says ready, accept ALU output
267 with m.If(self.wr.rel):
268 # tells ALU "thanks got it"
269 m.d.comb += self.alu.n_ready_i.eq(1)
270
271 # provide "done" signal: select req_rel for non-LD/ST, adr_rel for LD/ST
272 comb += self.done_o.eq((self.wr.rel & ~op_ldst) |
273 (self.adr_rel_o & op_ldst))
274
275 # put the register directly onto the output bus on a go_write
276 # this is "ALU mode". go_wr_i *must* be deasserted on next clock
277 with m.If(self.wr.go):
278 comb += self.data_o.eq(data_r)
279
280 # "LD/ST" mode: put the register directly onto the *address* bus
281 with m.If(self.go_ad_i | self.go_st_i):
282 comb += self.addr_o.eq(data_r)
283
284 # TODO: think about moving these to another module
285
286 if self.debugtest:
287 return m
288
289 # connect ST to memory. NOTE: unit *must* be set back
290 # to start again by dropping go_st_i on next clock
291 with m.If(self.stwd_mem_o):
292 wrport = self.mem.wrport
293 comb += wrport.addr.eq(self.addr_o)
294 comb += wrport.data.eq(src2_r)
295 comb += wrport.en.eq(1)
296
297 # connect LD to memory. NOTE: unit *must* be set back
298 # to start again by dropping go_ad_i on next clock
299 rdport = self.mem.rdport
300 ldd_r = Signal(self.rwid, reset_less=True) # Dest register
301 # latch LD-out
302 latchregister(m, rdport.data, ldd_r, ldlatch, "ldo_r")
303 sync += ldlatch.eq(self.load_mem_o)
304 with m.If(self.load_mem_o):
305 comb += rdport.addr.eq(self.addr_o)
306 # comb += rdport.en.eq(1) # only when transparent=False
307
308 # if LD-latch, put ld-reg out onto output
309 with m.If(ldlatch | self.load_mem_o):
310 comb += self.data_o.eq(ldd_r)
311
312 return m
313
314 def __iter__(self):
315 yield self.rd.go
316 yield self.go_ad_i
317 yield self.wr.go
318 yield self.go_st_i
319 yield self.issue_i
320 yield self.isalu_i
321 yield self.shadown_i
322 yield self.go_die_i
323 yield from self.oper_i.ports()
324 yield from self.src_i
325 yield self.busy_o
326 yield self.rd.rel
327 yield self.adr_rel_o
328 yield self.sto_rel_o
329 yield self.wr.rel
330 yield self.data_o
331 yield self.load_mem_o
332 yield self.stwd_mem_o
333
334 def ports(self):
335 return list(self)
336
337
338 def wait_for(sig):
339 v = (yield sig)
340 print("wait for", sig, v)
341 while True:
342 yield
343 v = (yield sig)
344 print(v)
345 if v:
346 break
347
348
349 def store(dut, src1, src2, imm, imm_ok=True):
350 yield dut.oper_i.insn_type.eq(InternalOp.OP_STORE)
351 yield dut.src1_i.eq(src1)
352 yield dut.src2_i.eq(src2)
353 yield dut.oper_i.imm_data.imm.eq(imm)
354 yield dut.oper_i.imm_data.imm_ok.eq(imm_ok)
355 yield dut.issue_i.eq(1)
356 yield
357 yield dut.issue_i.eq(0)
358 yield
359 yield dut.rd.go.eq(0b11)
360 yield from wait_for(dut.rd.rel)
361 yield dut.rd.go.eq(0)
362 yield from wait_for(dut.adr_rel_o)
363 yield dut.go_st_i.eq(1)
364 yield from wait_for(dut.sto_rel_o)
365 wait_for(dut.stwd_mem_o)
366 yield dut.go_st_i.eq(0)
367 yield
368
369
370 def load(dut, src1, src2, imm, imm_ok=True):
371 yield dut.oper_i.insn_type.eq(InternalOp.OP_LOAD)
372 yield dut.src1_i.eq(src1)
373 yield dut.src2_i.eq(src2)
374 yield dut.oper_i.imm_data.imm.eq(imm)
375 yield dut.oper_i.imm_data.imm_ok.eq(imm_ok)
376 yield dut.issue_i.eq(1)
377 yield
378 yield dut.issue_i.eq(0)
379 yield
380 yield dut.rd.go.eq(0b11)
381 yield from wait_for(dut.rd.rel)
382 yield dut.rd.go.eq(0)
383 yield from wait_for(dut.adr_rel_o)
384 yield dut.go_ad_i.eq(1)
385 yield from wait_for(dut.busy_o)
386 yield
387 data = (yield dut.data_o)
388 yield dut.go_ad_i.eq(0)
389 # wait_for(dut.stwd_mem_o)
390 return data
391
392
393 def add(dut, src1, src2, imm, imm_ok=False):
394 yield dut.oper_i.insn_type.eq(InternalOp.OP_ADD)
395 yield dut.src1_i.eq(src1)
396 yield dut.src2_i.eq(src2)
397 yield dut.oper_i.imm_data.imm.eq(imm)
398 yield dut.oper_i.imm_data.imm_ok.eq(imm_ok)
399 yield dut.issue_i.eq(1)
400 yield
401 yield dut.issue_i.eq(0)
402 yield
403 yield dut.rd.go.eq(1)
404 yield from wait_for(dut.rd.rel)
405 yield dut.rd.go.eq(0)
406 yield from wait_for(dut.wr.rel)
407 yield dut.wr.go.eq(1)
408 yield from wait_for(dut.busy_o)
409 yield
410 data = (yield dut.data_o)
411 yield dut.wr.go.eq(0)
412 yield
413 # wait_for(dut.stwd_mem_o)
414 return data
415
416
417 def scoreboard_sim(dut):
418 # two STs (different addresses)
419 yield from store(dut, 4, 3, 2)
420 yield from store(dut, 2, 9, 2)
421 yield
422 # two LDs (deliberately LD from the 1st address then 2nd)
423 data = yield from load(dut, 4, 0, 2)
424 assert data == 0x0003
425 data = yield from load(dut, 2, 0, 2)
426 assert data == 0x0009
427 yield
428
429 # now do an add
430 data = yield from add(dut, 4, 3, 0xfeed)
431 assert data == 0x7
432
433 # and an add-immediate
434 data = yield from add(dut, 4, 0xdeef, 2, imm_ok=True)
435 assert data == 0x6
436
437
438 class TestLDSTCompUnit(LDSTCompUnit):
439
440 def __init__(self, rwid):
441 from alu_hier import ALU
442 self.alu = alu = ALU(rwid)
443 self.mem = mem = TestMemory(rwid, 8)
444 LDSTCompUnit.__init__(self, rwid, alu, mem)
445
446 def elaborate(self, platform):
447 m = LDSTCompUnit.elaborate(self, platform)
448 m.submodules.mem = self.mem
449 return m
450
451
452 def test_scoreboard():
453
454 dut = TestLDSTCompUnit(16)
455 vl = rtlil.convert(dut, ports=dut.ports())
456 with open("test_ldst_comp.il", "w") as f:
457 f.write(vl)
458
459 run_simulation(dut, scoreboard_sim(dut), vcd_name='test_ldst_comp.vcd')
460
461
462 if __name__ == '__main__':
463 test_scoreboard()