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