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