update LDSTCompUnit comments
[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 LDST_OP_ADD = 0b0000 # plain ADD (src1 + src2) - use this ALU as an ADD
36 LDST_OP_SUB = 0b0001 # plain SUB (src1 - src2) - use this ALU as a SUB
37 LDST_OP_ADDI = 0b0010 # immed ADD (imm + src1)
38 LDST_OP_SUBI = 0b0011 # immed SUB (imm - src1)
39 LDST_OP_ST = 0b0110 # immed ADD plus LD op. ADD result is address
40 LDST_OP_LD = 0b1010 # immed ADD plus ST op. ADD result is address
41
42
43 class LDSTCompUnit(Elaboratable):
44 """ LOAD / STORE / ADD / SUB Computation Unit
45
46 Inputs
47 ------
48
49 * :rwid: register width
50 * :alu: an ALU module
51 * :mem: a Memory Module (read-write capable)
52
53 Control Signals (In)
54 --------------------
55
56 * :issue_i: LD/ST is being "issued".
57 * :isalu_i: ADD/SUB is being "issued" (aka issue_alu_i)
58 * :shadown_i: Inverted-shadow is being held (stops STORE *and* WRITE)
59 * :go_rd_i: read is being actioned (latches in src regs)
60 * :go_ad_i: address is being actioned (triggers actual mem LD)
61 * :go_st_i: store is being actioned (triggers actual mem STORE)
62 * :go_die_i: resets the unit back to "wait for issue"
63
64 Control Signals (Out)
65 ---------------------
66
67 * :busy_o: function unit is busy
68 * :rd_rel_o: request src1/src2
69 * :adr_rel_o: request address (from mem)
70 * :sto_rel_o: request store (to mem)
71 * :req_rel_o: request write (result)
72
73 Note: adr_rel, sto_rel, req_rel must all be acknowledged in a
74 single cycle.
75
76 Control Data (out)
77 ------------------
78 * :data_o: Dest out (LD or ALU)
79 * :addr_o: Address out (LD or ST)
80 """
81 def __init__(self, rwid, opwid, alu, mem):
82 self.opwid = opwid
83 self.rwid = rwid
84 self.alu = alu
85 self.mem = mem
86
87 self.counter = Signal(4)
88 self.go_rd_i = Signal(reset_less=True) # go read in
89 self.go_ad_i = Signal(reset_less=True) # go address in
90 self.go_wr_i = Signal(reset_less=True) # go write in
91 self.go_st_i = Signal(reset_less=True) # go store in
92 self.issue_i = Signal(reset_less=True) # fn issue in
93 self.isalu_i = Signal(reset_less=True) # fn issue as ALU in
94 self.shadown_i = Signal(reset=1) # shadow function, defaults to ON
95 self.go_die_i = Signal() # go die (reset)
96
97 self.oper_i = Signal(opwid, reset_less=True) # opcode in
98 self.imm_i = Signal(rwid, reset_less=True) # immediate in
99 self.src1_i = Signal(rwid, reset_less=True) # oper1 in
100 self.src2_i = Signal(rwid, reset_less=True) # oper2 in
101
102 self.busy_o = Signal(reset_less=True) # fn busy out
103 self.rd_rel_o = Signal(reset_less=True) # request src1/src2
104 self.adr_rel_o = Signal(reset_less=True) # request address (from mem)
105 self.sto_rel_o = Signal(reset_less=True) # request store (to mem)
106 self.req_rel_o = Signal(reset_less=True) # request write (result)
107 self.data_o = Signal(rwid, reset_less=True) # Dest out (LD or ALU)
108 self.addr_o = Signal(rwid, reset_less=True) # Address out (LD or ST)
109
110 # hmm... TODO... move these to outside of LDSTCompUnit?
111 self.load_mem_o = Signal(reset_less=True) # activate memory LOAD
112 self.stwd_mem_o = Signal(reset_less=True) # activate memory STORE
113 self.ld_o = Signal(reset_less=True) # operation is a LD
114 self.st_o = Signal(reset_less=True) # operation is a ST
115
116 def elaborate(self, platform):
117 m = Module()
118 comb = m.d.comb
119 sync = m.d.sync
120
121 m.submodules.alu = self.alu
122 #m.submodules.mem = self.mem
123 m.submodules.src_l = src_l = SRLatch(sync=False)
124 m.submodules.opc_l = opc_l = SRLatch(sync=False)
125 m.submodules.adr_l = adr_l = SRLatch(sync=False)
126 m.submodules.req_l = req_l = SRLatch(sync=False)
127 m.submodules.sto_l = sto_l = SRLatch(sync=False)
128
129 # shadow/go_die
130 reset_b = Signal(reset_less=True)
131 reset_w = Signal(reset_less=True)
132 reset_a = Signal(reset_less=True)
133 reset_s = Signal(reset_less=True)
134 reset_r = Signal(reset_less=True)
135 comb += reset_b.eq(self.go_st_i | self.go_wr_i | self.go_die_i)
136 comb += reset_w.eq(self.go_wr_i | self.go_die_i)
137 comb += reset_s.eq(self.go_st_i | self.go_die_i)
138 comb += reset_r.eq(self.go_rd_i | self.go_die_i)
139 # this one is slightly different, issue_alu_i selects go_wr_i)
140 a_sel = Mux(self.isalu_i, self.go_wr_i, self.go_ad_i)
141 comb += reset_a.eq(a_sel| self.go_die_i)
142
143 # opcode decode
144 op_alu = Signal(reset_less=True)
145 op_is_ld = Signal(reset_less=True)
146 op_is_st = Signal(reset_less=True)
147 op_ldst = Signal(reset_less=True)
148 op_is_imm = Signal(reset_less=True)
149
150 # src2 register
151 src2_r = Signal(self.rwid, reset_less=True)
152
153 # select immediate or src2 reg to add
154 src2_or_imm = Signal(self.rwid, reset_less=True)
155 src_sel = Signal(reset_less=True)
156
157 # issue can be either issue_i or issue_alu_i (isalu_i)
158 issue_i = Signal(reset_less=True)
159 comb += issue_i.eq(self.issue_i | self.isalu_i)
160
161 # Ripple-down the latches, each one set cancels the previous.
162 # NOTE: use sync to stop combinatorial loops.
163
164 # opcode latch - inverted so that busy resets to 0
165 sync += opc_l.s.eq(issue_i) # XXX NOTE: INVERTED FROM book!
166 sync += opc_l.r.eq(reset_b) # XXX NOTE: INVERTED FROM book!
167
168 # src operand latch
169 sync += src_l.s.eq(issue_i)
170 sync += src_l.r.eq(reset_r)
171
172 # addr latch
173 sync += adr_l.s.eq(self.go_rd_i)
174 sync += adr_l.r.eq(reset_a)
175
176 # dest operand latch
177 sync += req_l.s.eq(self.go_ad_i)
178 sync += req_l.r.eq(reset_w)
179
180 # store latch
181 sync += sto_l.s.eq(self.go_ad_i)
182 sync += sto_l.r.eq(reset_s)
183
184 # outputs: busy and release signals
185 busy_o = self.busy_o
186 comb += self.busy_o.eq(opc_l.q) # busy out
187 comb += self.rd_rel_o.eq(src_l.q & busy_o) # src1/src2 req rel
188 comb += self.sto_rel_o.eq(sto_l.q & busy_o & self.shadown_i & op_is_st)
189
190 # request release enabled based on if op is a LD/ST or a plain ALU
191 # if op is an ADD/SUB or a LD, req_rel activates.
192 wr_q = Signal(reset_less=True)
193 comb += wr_q.eq(req_l.q & (~op_ldst | op_is_ld))
194
195 alulatch = Signal(reset_less=True)
196 comb += alulatch.eq((op_ldst & self.adr_rel_o) | \
197 (~op_ldst & self.req_rel_o))
198
199 # only proceed if ALU says its output is valid
200 with m.If(self.alu.n_valid_o):
201
202 # write req release out. waits until shadow is dropped.
203 comb += self.req_rel_o.eq(wr_q & busy_o & self.shadown_i)
204 # address release only happens on LD/ST, and is shadowed.
205 comb += self.adr_rel_o.eq(adr_l.q & op_ldst & busy_o & \
206 self.shadown_i)
207 # when output latch is ready, and ALU says ready, accept ALU output
208 with m.If(self.req_rel_o):
209 m.d.comb += self.alu.n_ready_i.eq(1) # tells ALU "thanks got it"
210
211 # select immediate if opcode says so. however also change the latch
212 # to trigger *from* the opcode latch instead.
213 comb += src_sel.eq(Mux(op_is_imm, opc_l.qn, src_l.q))
214 comb += src2_or_imm.eq(Mux(op_is_imm, self.imm_i, self.src2_i))
215
216 # create a latch/register for src1/src2 (include immediate select)
217 latchregister(m, self.src1_i, self.alu.a, src_l.q)
218 latchregister(m, self.src2_i, src2_r, src_l.q)
219 latchregister(m, src2_or_imm, self.alu.b, src_sel)
220
221 # create a latch/register for the operand
222 oper_r = Signal(self.opwid, reset_less=True) # Dest register
223 latchregister(m, self.oper_i, oper_r, self.issue_i)
224 alu_op = Cat(op_alu, 0, op_is_imm) # using alu_hier, here.
225 comb += self.alu.op.eq(alu_op)
226
227 # and one for the output from the ALU
228 data_r = Signal(self.rwid, reset_less=True) # Dest register
229 latchregister(m, self.alu.o, data_r, alulatch)
230
231 # decode bits of operand (latched)
232 comb += op_alu.eq(oper_r[0])
233 comb += op_is_imm.eq(oper_r[1])
234 comb += op_is_ld.eq(oper_r[2])
235 comb += op_is_st.eq(oper_r[3])
236 comb += op_ldst.eq(op_is_ld | op_is_st)
237 comb += self.load_mem_o.eq(op_is_ld & self.go_ad_i)
238 comb += self.stwd_mem_o.eq(op_is_st & self.go_st_i)
239 comb += self.ld_o.eq(op_is_ld)
240 comb += self.st_o.eq(op_is_st)
241
242 # on a go_read, tell the ALU we're accepting data.
243 # NOTE: this spells TROUBLE if the ALU isn't ready!
244 # go_read is only valid for one clock!
245 with m.If(self.go_rd_i): # src operands ready, GO!
246 with m.If(~self.alu.p_ready_o): # no ACK yet
247 m.d.comb += self.alu.p_valid_i.eq(1) # so indicate valid
248
249 # put the register directly onto the output bus on a go_write
250 with m.If(self.go_wr_i):
251 comb += self.data_o.eq(data_r)
252
253 # put the register directly onto the address bus
254 with m.If(self.go_ad_i):
255 comb += self.addr_o.eq(data_r)
256
257 # TODO: think about moving this to another module
258 # connect ST to memory
259 with m.If(self.stwd_mem_o):
260 wrport = self.mem.wrport
261 comb += wrport.addr.eq(self.addr_o)
262 comb += wrport.data.eq(src2_r)
263 comb += wrport.en.eq(1)
264
265 return m
266
267 def __iter__(self):
268 yield self.go_rd_i
269 yield self.go_ad_i
270 yield self.go_wr_i
271 yield self.go_st_i
272 yield self.issue_i
273 yield self.isalu_i
274 yield self.shadown_i
275 yield self.go_die_i
276 yield self.oper_i
277 yield self.imm_i
278 yield self.src1_i
279 yield self.src2_i
280 yield self.busy_o
281 yield self.rd_rel_o
282 yield self.adr_rel_o
283 yield self.sto_rel_o
284 yield self.req_rel_o
285 yield self.data_o
286 yield self.load_mem_o
287 yield self.stwd_mem_o
288
289 def ports(self):
290 return list(self)
291
292
293 def scoreboard_sim(dut):
294 yield dut.dest_i.eq(1)
295 yield dut.issue_i.eq(1)
296 yield
297 yield dut.issue_i.eq(0)
298 yield
299 yield dut.src1_i.eq(1)
300 yield dut.issue_i.eq(1)
301 yield
302 yield
303 yield
304 yield dut.issue_i.eq(0)
305 yield
306 yield dut.go_read_i.eq(1)
307 yield
308 yield dut.go_read_i.eq(0)
309 yield
310 yield dut.go_write_i.eq(1)
311 yield
312 yield dut.go_write_i.eq(0)
313 yield
314
315
316 class TestLDSTCompUnit(LDSTCompUnit):
317
318 def __init__(self, rwid, opwid):
319 from alu_hier import ALU
320 self.alu = alu = ALU(rwid)
321 self.mem = mem = TestMemory(rwid, 8)
322 LDSTCompUnit.__init__(self, rwid, opwid, alu, mem)
323
324 def elaborate(self, platform):
325 m = LDSTCompUnit.elaborate(self, platform)
326 m.submodules.mem = self.mem
327 return m
328
329
330 def test_scoreboard():
331
332 dut = TestLDSTCompUnit(16, 4)
333 vl = rtlil.convert(dut, ports=dut.ports())
334 with open("test_ldst_comp.il", "w") as f:
335 f.write(vl)
336
337 run_simulation(dut, scoreboard_sim(dut), vcd_name='test_ldst_comp.vcd')
338
339 if __name__ == '__main__':
340 test_scoreboard()