LD appears to be working as well although there is an oddness in the gtkwave
[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 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
49 class LDSTCompUnit(Elaboratable):
50 """ LOAD / STORE / ADD / SUB Computation Unit
51
52 Inputs
53 ------
54
55 * :rwid: register width
56 * :alu: an ALU module
57 * :mem: a Memory Module (read-write capable)
58
59 Control Signals (In)
60 --------------------
61
62 * :oper_i: operation being carried out (LDST_OP_ADD, LDST_OP_LD)
63 * :issue_i: LD/ST is being "issued".
64 * :isalu_i: ADD/SUB is being "issued" (aka issue_alu_i)
65 * :shadown_i: Inverted-shadow is being held (stops STORE *and* WRITE)
66 * :go_rd_i: read is being actioned (latches in src regs)
67 * :go_wr_i: write mode (exactly like ALU CompUnit)
68 * :go_ad_i: address is being actioned (triggers actual mem LD)
69 * :go_st_i: store is being actioned (triggers actual mem STORE)
70 * :go_die_i: resets the unit back to "wait for issue"
71
72 Control Signals (Out)
73 ---------------------
74
75 * :busy_o: function unit is busy
76 * :rd_rel_o: request src1/src2
77 * :adr_rel_o: request address (from mem)
78 * :sto_rel_o: request store (to mem)
79 * :req_rel_o: request write (result)
80 * :load_mem_o: activate memory LOAD
81 * :stwd_mem_o: activate memory STORE
82
83 Note: load_mem_o, stwd_mem_o and req_rel_o MUST all be acknowledged
84 in a single cycle and the CompUnit set back to doing another op.
85 This means deasserting go_st_i, go_ad_i or go_wr_i as appropriate
86 depending on whether the operation is a STORE, LD, or a straight
87 ALU operation respectively.
88
89 Control Data (out)
90 ------------------
91 * :data_o: Dest out (LD or ALU)
92 * :addr_o: Address out (LD or ST)
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.data_o = Signal(rwid, reset_less=True) # Dest out (LD or ALU)
121 self.addr_o = Signal(rwid, reset_less=True) # Address out (LD or ST)
122
123 # hmm... TODO... move these to outside of LDSTCompUnit?
124 self.load_mem_o = Signal(reset_less=True) # activate memory LOAD
125 self.stwd_mem_o = Signal(reset_less=True) # activate memory STORE
126 self.ld_o = Signal(reset_less=True) # operation is a LD
127 self.st_o = Signal(reset_less=True) # operation is a ST
128
129 def elaborate(self, platform):
130 m = Module()
131 comb = m.d.comb
132 sync = m.d.sync
133
134 m.submodules.alu = self.alu
135 #m.submodules.mem = self.mem
136 m.submodules.src_l = src_l = SRLatch(sync=False, name="src")
137 m.submodules.opc_l = opc_l = SRLatch(sync=False, name="opc")
138 m.submodules.adr_l = adr_l = SRLatch(sync=False, name="adr")
139 m.submodules.req_l = req_l = SRLatch(sync=False, name="req")
140 m.submodules.sto_l = sto_l = SRLatch(sync=False, name="sto")
141
142 # shadow/go_die
143 reset_b = Signal(reset_less=True)
144 reset_w = Signal(reset_less=True)
145 reset_a = Signal(reset_less=True)
146 reset_s = Signal(reset_less=True)
147 reset_r = Signal(reset_less=True)
148 comb += reset_b.eq(self.go_st_i | self.go_wr_i | self.go_die_i)
149 comb += reset_w.eq(self.go_wr_i | self.go_die_i)
150 comb += reset_s.eq(self.go_st_i | self.go_die_i)
151 comb += reset_r.eq(self.go_rd_i | self.go_die_i)
152 # this one is slightly different, issue_alu_i selects go_wr_i)
153 a_sel = Mux(self.isalu_i, self.go_wr_i, self.go_ad_i)
154 comb += reset_a.eq(a_sel| self.go_die_i)
155
156 # opcode decode
157 op_alu = Signal(reset_less=True)
158 op_is_ld = Signal(reset_less=True)
159 op_is_st = Signal(reset_less=True)
160 op_ldst = Signal(reset_less=True)
161 op_is_imm = Signal(reset_less=True)
162
163 # src2 register
164 src2_r = Signal(self.rwid, reset_less=True)
165
166 # select immediate or src2 reg to add
167 src2_or_imm = Signal(self.rwid, reset_less=True)
168 src_sel = Signal(reset_less=True)
169
170 # issue can be either issue_i or issue_alu_i (isalu_i)
171 issue_i = Signal(reset_less=True)
172 comb += issue_i.eq(self.issue_i | self.isalu_i)
173
174 # Ripple-down the latches, each one set cancels the previous.
175 # NOTE: use sync to stop combinatorial loops.
176
177 # opcode latch - inverted so that busy resets to 0
178 sync += opc_l.s.eq(issue_i) # XXX NOTE: INVERTED FROM book!
179 sync += opc_l.r.eq(reset_b) # XXX NOTE: INVERTED FROM book!
180
181 # src operand latch
182 sync += src_l.s.eq(issue_i)
183 sync += src_l.r.eq(reset_r)
184
185 # addr latch
186 sync += adr_l.s.eq(self.go_rd_i)
187 sync += adr_l.r.eq(reset_a)
188
189 # dest operand latch
190 sync += req_l.s.eq(self.go_ad_i|self.go_st_i)
191 sync += req_l.r.eq(reset_w)
192
193 # store latch
194 sync += sto_l.s.eq(issue_i)#self.go_ad_i)
195 sync += sto_l.r.eq(reset_s)
196
197 # outputs: busy and release signals
198 busy_o = self.busy_o
199 comb += self.busy_o.eq(opc_l.q) # busy out
200 comb += self.rd_rel_o.eq(src_l.q & busy_o) # src1/src2 req rel
201 comb += self.sto_rel_o.eq(sto_l.q & busy_o & self.shadown_i & op_is_st)
202
203 # request release enabled based on if op is a LD/ST or a plain ALU
204 # if op is an ADD/SUB or a LD, req_rel activates.
205 wr_q = Signal(reset_less=True)
206 comb += wr_q.eq(req_l.q & (~op_ldst | op_is_ld))
207
208 alulatch = Signal(reset_less=True)
209 comb += alulatch.eq((op_ldst & self.adr_rel_o) | \
210 (~op_ldst & self.req_rel_o))
211
212 # select immediate if opcode says so. however also change the latch
213 # to trigger *from* the opcode latch instead.
214 comb += src_sel.eq(Mux(op_is_imm, opc_l.qn, src_l.q))
215 comb += src2_or_imm.eq(Mux(op_is_imm, self.imm_i, self.src2_i))
216
217 # create a latch/register for src1/src2 (include immediate select)
218 latchregister(m, self.src1_i, self.alu.a, src_l.q, name="src1_r")
219 latchregister(m, self.src2_i, src2_r, src_l.q, name="src2_r")
220 latchregister(m, src2_or_imm, self.alu.b, src_sel, name="imm_r")
221
222 # create a latch/register for the operand
223 oper_r = Signal(self.opwid, reset_less=True) # Dest register
224 latchregister(m, self.oper_i, oper_r, self.issue_i, name="operi_r")
225 alu_op = Cat(op_alu, 0, op_is_imm) # using alu_hier, here.
226 comb += self.alu.op.eq(alu_op)
227
228 # and one for the output from the ALU
229 data_r = Signal(self.rwid, reset_less=True) # Dest register
230 latchregister(m, self.alu.o, data_r, alulatch, "aluo_r")
231
232 # decode bits of operand (latched)
233 comb += op_alu.eq(oper_r[BIT0_ADD]) # ADD/SUB
234 comb += op_is_imm.eq(oper_r[BIT1_SRC]) # IMMED/reg
235 comb += op_is_st.eq(oper_r[BIT2_ST]) # OP is ST
236 comb += op_is_ld.eq(oper_r[BIT3_LD]) # OP is LD
237 comb += op_ldst.eq(op_is_ld | op_is_st)
238 comb += self.load_mem_o.eq(op_is_ld & self.go_ad_i)
239 comb += self.stwd_mem_o.eq(op_is_st & self.go_st_i)
240 comb += self.ld_o.eq(op_is_ld)
241 comb += self.st_o.eq(op_is_st)
242
243 # on a go_read, tell the ALU we're accepting data.
244 # NOTE: this spells TROUBLE if the ALU isn't ready!
245 # go_read is only valid for one clock!
246 with m.If(self.go_rd_i): # src operands ready, GO!
247 with m.If(~self.alu.p_ready_o): # no ACK yet
248 m.d.comb += self.alu.p_valid_i.eq(1) # so indicate valid
249
250 # only proceed if ALU says its output is valid
251 with m.If(self.alu.n_valid_o):
252 # write req release out. waits until shadow is dropped.
253 comb += self.req_rel_o.eq(wr_q & busy_o & self.shadown_i)
254 # address release only happens on LD/ST, and is shadowed.
255 comb += self.adr_rel_o.eq(adr_l.q & op_ldst & busy_o & \
256 self.shadown_i)
257 # when output latch is ready, and ALU says ready, accept ALU output
258 with m.If(self.req_rel_o):
259 m.d.comb += self.alu.n_ready_i.eq(1) # tells ALU "thanks got it"
260
261 # put the register directly onto the output bus on a go_write
262 # this is "ALU mode". go_wr_i *must* be deasserted on next clock
263 with m.If(self.go_wr_i):
264 comb += self.data_o.eq(data_r)
265
266 # "LD/ST" mode: put the register directly onto the *address* bus
267 with m.If(self.go_ad_i | self.go_st_i):
268 comb += self.addr_o.eq(data_r)
269
270 # TODO: think about moving these to another module
271
272 # connect ST to memory. NOTE: unit *must* be set back
273 # to start again by dropping go_st_i on next clock
274 with m.If(self.stwd_mem_o):
275 wrport = self.mem.wrport
276 comb += wrport.addr.eq(self.addr_o)
277 comb += wrport.data.eq(src2_r)
278 comb += wrport.en.eq(1)
279
280 # connect LD to memory. NOTE: unit *must* be set back
281 # to start again by dropping go_ad_i on next clock
282 with m.If(self.load_mem_o):
283 rdport = self.mem.rdport
284 comb += rdport.addr.eq(self.addr_o)
285 comb += self.data_o.eq(rdport.data)
286 # comb += rdport.en.eq(1) # only when transparent=False
287
288 return m
289
290 def __iter__(self):
291 yield self.go_rd_i
292 yield self.go_ad_i
293 yield self.go_wr_i
294 yield self.go_st_i
295 yield self.issue_i
296 yield self.isalu_i
297 yield self.shadown_i
298 yield self.go_die_i
299 yield self.oper_i
300 yield self.imm_i
301 yield self.src1_i
302 yield self.src2_i
303 yield self.busy_o
304 yield self.rd_rel_o
305 yield self.adr_rel_o
306 yield self.sto_rel_o
307 yield self.req_rel_o
308 yield self.data_o
309 yield self.load_mem_o
310 yield self.stwd_mem_o
311
312 def ports(self):
313 return list(self)
314
315 def wait_for(sig):
316 v = (yield sig)
317 print ("wait for", sig, v)
318 while True:
319 yield
320 v = (yield sig)
321 print (v)
322 if v:
323 break
324
325 def store(dut):
326 yield dut.oper_i.eq(LDST_OP_ST)
327 yield dut.src1_i.eq(4)
328 yield dut.src2_i.eq(9)
329 yield dut.imm_i.eq(2)
330 yield dut.issue_i.eq(1)
331 yield
332 yield dut.issue_i.eq(0)
333 yield
334 yield dut.go_rd_i.eq(1)
335 yield from wait_for(dut.rd_rel_o)
336 yield dut.go_rd_i.eq(0)
337 yield from wait_for(dut.adr_rel_o)
338 yield dut.go_st_i.eq(1)
339 yield from wait_for(dut.sto_rel_o)
340 wait_for(dut.stwd_mem_o)
341 yield dut.go_st_i.eq(0)
342 yield
343
344
345 def load(dut):
346 yield dut.oper_i.eq(LDST_OP_LD)
347 yield dut.src1_i.eq(4)
348 yield dut.src2_i.eq(9)
349 yield dut.imm_i.eq(2)
350 yield dut.issue_i.eq(1)
351 yield
352 yield dut.issue_i.eq(0)
353 yield
354 yield dut.go_rd_i.eq(1)
355 yield from wait_for(dut.rd_rel_o)
356 yield dut.go_rd_i.eq(0)
357 yield from wait_for(dut.adr_rel_o)
358 yield dut.go_ad_i.eq(1)
359 yield from wait_for(dut.busy_o)
360 #wait_for(dut.stwd_mem_o)
361 yield dut.go_ad_i.eq(0)
362 data = (yield dut.data_o)
363 print ("read", data)
364 assert data != 0x0009
365 yield
366
367 def scoreboard_sim(dut):
368 yield from store(dut)
369 yield from load(dut)
370
371
372 class TestLDSTCompUnit(LDSTCompUnit):
373
374 def __init__(self, rwid, opwid):
375 from alu_hier import ALU
376 self.alu = alu = ALU(rwid)
377 self.mem = mem = TestMemory(rwid, 8)
378 LDSTCompUnit.__init__(self, rwid, opwid, alu, mem)
379
380 def elaborate(self, platform):
381 m = LDSTCompUnit.elaborate(self, platform)
382 m.submodules.mem = self.mem
383 return m
384
385
386 def test_scoreboard():
387
388 dut = TestLDSTCompUnit(16, 4)
389 vl = rtlil.convert(dut, ports=dut.ports())
390 with open("test_ldst_comp.il", "w") as f:
391 f.write(vl)
392
393 run_simulation(dut, scoreboard_sim(dut), vcd_name='test_ldst_comp.vcd')
394
395 if __name__ == '__main__':
396 test_scoreboard()