convert DependencyRow to multiple destination latches
[soc.git] / src / soc / scoreboard / dependence_cell.py
1 # (DO NOT REMOVE THESE NOTICES)
2 # SPDX-License-Identifier: LGPLv3+
3 # Copyright (C) 2019, 2020, 2021 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
4 # Part of the Libre-SOC Project.
5 # Sponsored by NLnet EU Grant No: 825310 and 825322
6 # Sponsored by NGI POINTER EU Grant No: 871528
7
8 from nmigen.compat.sim import run_simulation
9 from nmigen.cli import verilog, rtlil
10 from nmigen import Module, Signal, Elaboratable, Cat, Repl
11 from nmutil.latch import SRLatch
12
13
14 class DependencyRow(Elaboratable):
15 """ implements 11.4.7 mitch alsup dependence cell, p27
16 adjusted to be clock-sync'd on rising edge only.
17 mitch design (as does 6600) requires alternating rising/falling clock
18
19 * SET mode: issue_i HI, go_i LO, reg_i HI - register is captured
20 - FWD is DISABLED (~issue_i)
21 - RSEL DISABLED
22 * QRY mode: issue_i LO, go_i LO, haz_i HI - FWD is ASSERTED
23 reg_i HI - ignored
24 * GO mode : issue_i LO, go_i HI - RSEL is ASSERTED
25 haz_i HI - FWD still can be ASSERTED
26
27 FWD assertion (hazard protection) therefore still occurs in both
28 Query and Go Modes, for this cycle, due to the cq register
29
30 GO mode works for one cycle, again due to the cq register capturing
31 the latch output. Without the cq register, the SR Latch (which is
32 asynchronous) would be reset at the exact moment that GO was requested,
33 and the RSEL would be garbage.
34 """
35 def __init__(self, n_reg, n_src, n_dst, cancel_mode=False):
36 self.cancel_mode = cancel_mode
37 self.n_reg = n_reg
38 self.n_src = n_src
39 self.n_dst = n_dst
40 # arrays
41 src = []
42 rsel = []
43 fwd = []
44 for i in range(n_src):
45 j = i + 1 # name numbering to match src1/src2
46 src.append(Signal(n_reg, name="src%d" % j, reset_less=True))
47 rsel.append(Signal(n_reg, name="src%d_rsel_o" % j, reset_less=True))
48 fwd.append(Signal(n_reg, name="src%d_fwd_o" % j, reset_less=True))
49 dst = []
50 dsel = []
51 dfwd = []
52 for i in range(n_dst):
53 j = i + 1 # name numbering to match src1/src2
54 dst.append(Signal(n_reg, name="dst%d" % j, reset_less=True))
55 dsel.append(Signal(n_reg, name="dst%d_rsel_o" % j, reset_less=True))
56 dfwd.append(Signal(n_reg, name="dst%d_fwd_o" % j, reset_less=True))
57
58 # inputs
59 self.dst_i = tuple(dst) # Dest in (top)
60 self.src_i = tuple(src) # operands in (top)
61 self.issue_i = Signal(reset_less=True) # Issue in (top)
62
63 self.rd_pend_i = Signal(n_reg, reset_less=True) # Read pend in (top)
64 self.wr_pend_i = Signal(n_reg, reset_less=True) # Write pend in (top)
65 self.v_rd_rsel_o = Signal(n_reg, reset_less=True) # Read pend out (bot)
66 self.v_wr_rsel_o = Signal(n_reg, reset_less=True) # Write pend out (bot)
67
68 self.go_wr_i = Signal(reset_less=True) # Go Write in (left)
69 self.go_rd_i = Signal(reset_less=True) # Go Read in (left)
70 if self.cancel_mode:
71 self.go_die_i = Signal(n_reg, reset_less=True) # Go Die in (left)
72 else:
73 self.go_die_i = Signal(reset_less=True) # Go Die in (left)
74
75 # for Register File Select Lines (vertical)
76 self.dst_rsel_o = tuple(dsel) # dest reg sel (bot)
77 self.src_rsel_o = tuple(rsel) # src reg sel (bot)
78
79 # for Function Unit "forward progress" (horizontal)
80 self.dst_fwd_o = tuple(dfwd) # dest FU fw (right)
81 self.src_fwd_o = tuple(fwd) # src FU fw (right)
82
83 # for temporary (transitional) compatibility with old API
84 # number of dests used to be 1 (one) - increasing to n_dst
85 self.dest_i = self.dst_i[0]
86 self.dest_rsel_o = self.dst_rsel_o[0]
87 self.dest_fwd_o = self.dst_fwd_o[0]
88
89 def elaborate(self, platform):
90 m = Module()
91 # create source and dest SRLatches
92 dst_c = []
93 for i in range(self.n_dst):
94 dst_l = SRLatch(sync=False, llen=self.n_reg)
95 m.submodules["dst%d_c" % (i+1)] = dst_l
96 dst_c.append(dst_l)
97
98 src_c = []
99 for i in range(self.n_src):
100 src_l = SRLatch(sync=False, llen=self.n_reg)
101 m.submodules["src%d_c" % (i+1)] = src_l
102 src_c.append(src_l)
103
104 # connect go_rd / go_wr (dest->wr, src->rd)
105 wr_die = Signal(self.n_reg, reset_less=True)
106 rd_die = Signal(self.n_reg, reset_less=True)
107 if self.cancel_mode:
108 go_die = self.go_die_i
109 else:
110 go_die = Repl(self.go_die_i, self.n_reg)
111 m.d.comb += wr_die.eq(Repl(self.go_wr_i, self.n_reg) | go_die)
112 m.d.comb += rd_die.eq(Repl(self.go_rd_i, self.n_reg) | go_die)
113 for i in range(self.n_dst):
114 m.d.comb += dst_c[i].r.eq(wr_die)
115 for i in range(self.n_src):
116 m.d.comb += src_c[i].r.eq(rd_die)
117
118 # connect input reg bit (unary)
119 i_ext = Repl(self.issue_i, self.n_reg)
120 for i in range(self.n_dst):
121 m.d.comb += dst_c[i].s.eq(i_ext & self.dst_i[i])
122 for i in range(self.n_src):
123 m.d.comb += src_c[i].s.eq(i_ext & self.src_i[i])
124
125 # connect up hazard checks: read-after-write and write-after-read
126 for i in range(self.n_dst):
127 m.d.comb += self.dst_fwd_o[i].eq(dst_c[i].q & self.rd_pend_i)
128 for i in range(self.n_src):
129 m.d.comb += self.src_fwd_o[i].eq(src_c[i].q & self.wr_pend_i)
130
131 # connect reg-sel outputs
132 rd_ext = Repl(self.go_rd_i, self.n_reg)
133 wr_ext = Repl(self.go_wr_i, self.n_reg)
134 for i in range(self.n_dst):
135 m.d.comb += self.dst_rsel_o[i].eq(dst_c[i].qlq & wr_ext)
136 for i in range(self.n_src):
137 m.d.comb += self.src_rsel_o[i].eq(src_c[i].qlq & rd_ext)
138
139 # to be accumulated to indicate if register is in use (globally)
140 # after ORing, is fed back in to rd_pend_i / wr_pend_i
141 src_q = []
142 for i in range(self.n_src):
143 src_q.append(src_c[i].qlq)
144 m.d.comb += self.v_rd_rsel_o.eq(Cat(*src_q).bool())
145 dst_q = []
146 for i in range(self.n_dst):
147 dst_q.append(dst_c[i].qlq)
148 m.d.comb += self.v_wr_rsel_o.eq(Cat(*dst_q).bool())
149
150 return m
151
152 def __iter__(self):
153 yield from self.dst_i
154 yield from self.src_i
155 yield self.rd_pend_i
156 yield self.wr_pend_i
157 yield self.issue_i
158 yield self.go_wr_i
159 yield self.go_rd_i
160 yield self.go_die_i
161 yield from self.dst_rsel_o
162 yield from self.src_rsel_o
163 yield from self.dst_fwd_o
164 yield from self.src_fwd_o
165
166 def ports(self):
167 return list(self)
168
169
170 # XXX not up-to-date but hey
171 def dcell_sim(dut):
172 yield dut.dest_i.eq(1)
173 yield dut.issue_i.eq(1)
174 yield
175 yield dut.issue_i.eq(0)
176 yield
177 yield dut.src_i[0].eq(1)
178 yield dut.issue_i.eq(1)
179 yield
180 yield
181 yield
182 yield dut.issue_i.eq(0)
183 yield
184 yield dut.go_rd_i.eq(1)
185 yield
186 yield dut.go_rd_i.eq(0)
187 yield
188 yield dut.go_wr_i.eq(1)
189 yield
190 yield dut.go_wr_i.eq(0)
191 yield
192
193 def test_dcell():
194 dut = DependencyRow(4, 2, 2, True)
195 vl = rtlil.convert(dut, ports=dut.ports())
196 with open("test_drow.il", "w") as f:
197 f.write(vl)
198
199 run_simulation(dut, dcell_sim(dut), vcd_name='test_dcell.vcd')
200
201 if __name__ == '__main__':
202 test_dcell()