3 not in any way intended for production use. connects up FunctionUnits to
4 Register Files in a brain-dead fashion that only permits one and only one
5 Function Unit to be operational.
7 the principle here is to take the Function Units, analyse their regspecs,
8 and turn their requirements for access to register file read/write ports
9 into groupings by Register File and Register File Port name.
11 under each grouping - by regfile/port - a list of Function Units that
12 need to connect to that port is created. as these are a contended
13 resource a "Broadcast Bus" per read/write port is then also created,
14 with access to it managed by a PriorityPicker.
16 the brain-dead part of this module is that even though there is no
17 conflict of access, regfile read/write hazards are *not* analysed,
18 and consequently it is safer to wait for the Function Unit to complete
19 before allowing a new instruction to proceed.
22 from nmigen
import Elaboratable
, Module
, Signal
23 from nmigen
.cli
import rtlil
25 from nmutil
.picker
import PriorityPicker
26 from nmutil
.util
import treereduce
28 from soc
.fu
.compunits
.compunits
import AllFunctionUnits
29 from soc
.regfile
.regfiles
import RegFiles
30 from soc
.decoder
.power_decoder
import create_pdecode
31 from soc
.decoder
.power_decoder2
import PowerDecode2
32 from soc
.decoder
.decode2execute1
import Data
33 from soc
.experiment
.l0_cache
import TstL0CacheBuffer
# test only
34 from soc
.experiment
.testmem
import TestMemory
# test only for instructions
38 # helper function for reducing a list of signals down to a parallel
40 def ortreereduce(tree
, attr
="data_o"):
41 return treereduce(tree
, operator
.or_
, lambda x
: getattr(x
, attr
))
43 # helper function to place full regs declarations first
44 def sort_fuspecs(fuspecs
):
46 for (regname
, fspec
) in fuspecs
.items():
47 if regname
.startswith("full"):
48 res
.append((regname
, fspec
))
49 for (regname
, fspec
) in fuspecs
.items():
50 if not regname
.startswith("full"):
51 res
.append((regname
, fspec
))
52 return res
# enumerate(res)
55 class NonProductionCore(Elaboratable
):
56 def __init__(self
, addrwid
=6, idepth
=16):
57 # single LD/ST funnel for memory access
58 self
.l0
= TstL0CacheBuffer(n_units
=1, regwid
=64, addrwid
=addrwid
)
59 pi
= self
.l0
.l0
.dports
[0].pi
61 # Test Instruction memory
62 #self.imem = TestMemory(32, idepth)
64 # function units (only one each)
65 self
.fus
= AllFunctionUnits(pilist
=[pi
], addrwid
=addrwid
)
67 # register files (yes plural)
68 self
.regs
= RegFiles()
71 pdecode
= create_pdecode()
72 self
.pdecode2
= PowerDecode2(pdecode
) # instruction decoder
74 # issue/valid/busy signalling
75 self
.ivalid_i
= self
.pdecode2
.e
.valid
# instruction is valid
76 self
.issue_i
= Signal(reset_less
=True)
77 self
.busy_o
= Signal(reset_less
=True)
80 self
.bigendian_i
= self
.pdecode2
.dec
.bigendian
81 self
.raw_opcode_i
= self
.pdecode2
.dec
.raw_opcode_in
83 def elaborate(self
, platform
):
86 m
.submodules
.pdecode2
= dec2
= self
.pdecode2
87 m
.submodules
.fus
= self
.fus
88 m
.submodules
.l0
= l0
= self
.l0
89 #m.submodules.imem = imem = self.imem
90 self
.regs
.elaborate_into(m
, platform
)
94 fu_bitdict
= self
.connect_instruction(m
)
95 self
.connect_rdports(m
, fu_bitdict
)
96 self
.connect_wrports(m
, fu_bitdict
)
100 def connect_instruction(self
, m
):
101 comb
, sync
= m
.d
.comb
, m
.d
.sync
105 # enable-signals for each FU, get one bit for each FU (by name)
106 fu_enable
= Signal(len(fus
), reset_less
=True)
108 for i
, funame
in enumerate(fus
.keys()):
109 fu_bitdict
[funame
] = fu_enable
[i
]
111 # connect up instructions. only one is enabled at any given time
112 for funame
, fu
in fus
.items():
113 fnunit
= fu
.fnunit
.value
114 enable
= Signal(name
="en_%s" % funame
, reset_less
=True)
115 comb
+= enable
.eq(self
.ivalid_i
& (dec2
.e
.fn_unit
& fnunit
).bool())
117 comb
+= fu
.oper_i
.eq_from_execute1(dec2
.e
)
118 comb
+= fu
.issue_i
.eq(self
.issue_i
)
119 comb
+= self
.busy_o
.eq(fu
.busy_o
)
120 rdmask
= dec2
.rdflags(fu
)
121 comb
+= fu
.rdmaskn
.eq(~rdmask
)
122 comb
+= fu_bitdict
[funame
].eq(enable
)
126 def connect_rdports(self
, m
, fu_bitdict
):
127 """connect read ports
129 orders the read regspecs into a dict-of-dicts, by regfile, by
130 regport name, then connects all FUs that want that regport by
131 way of a PriorityPicker.
133 comb
, sync
= m
.d
.comb
, m
.d
.sync
137 # dictionary of lists of regfile read ports
138 byregfiles_rd
, byregfiles_rdspec
= self
.get_byregfiles(True)
140 # okaay, now we need a PriorityPicker per regfile per regfile port
141 # loootta pickers... peter piper picked a pack of pickled peppers...
143 for regfile
, spec
in byregfiles_rd
.items():
144 fuspecs
= byregfiles_rdspec
[regfile
]
145 rdpickers
[regfile
] = {}
147 # for each named regfile port, connect up all FUs to that port
148 for (regname
, fspec
) in sort_fuspecs(fuspecs
):
149 print ("connect rd", regname
, fspec
)
151 # get the regfile specs for this regfile port
152 (rf
, read
, write
, wid
, fuspec
) = fspec
153 name
= "rdflag_%s_%s" % (regfile
, regname
)
154 rdflag
= Signal(name
=name
, reset_less
=True)
155 comb
+= rdflag
.eq(rf
)
157 # select the required read port. these are pre-defined sizes
158 print (rpidx
, regfile
, regs
.rf
.keys())
159 rport
= regs
.rf
[regfile
.lower()].r_ports
[rpidx
]
161 # create a priority picker to manage this port
162 rdpickers
[regfile
][rpidx
] = rdpick
= PriorityPicker(len(fuspec
))
163 setattr(m
.submodules
, "rdpick_%s_%s" % (regfile
, rpidx
), rdpick
)
165 # connect the regspec "reg select" number to this port
166 with m
.If(rdpick
.en_o
):
167 comb
+= rport
.ren
.eq(read
)
169 # connect up the FU req/go signals, and the reg-read to the FU
170 # and create a Read Broadcast Bus
171 for pi
, (funame
, fu
, idx
) in enumerate(fuspec
):
174 # connect request-read to picker input, and output to go-rd
175 fu_active
= fu_bitdict
[funame
]
176 pick
= fu
.rd_rel_o
[idx
] & fu_active
& rdflag
177 comb
+= rdpick
.i
[pi
].eq(pick
)
178 comb
+= fu
.go_rd_i
[idx
].eq(rdpick
.o
[pi
])
180 # connect regfile port to input, creating a Broadcast Bus
181 print ("reg connect widths",
182 regfile
, regname
, pi
, funame
,
183 src
.shape(), rport
.data_o
.shape())
184 comb
+= src
.eq(rport
.data_o
) # all FUs connect to same port
186 def connect_wrports(self
, m
, fu_bitdict
):
187 """connect write ports
189 orders the write regspecs into a dict-of-dicts, by regfile,
190 by regport name, then connects all FUs that want that regport
191 by way of a PriorityPicker.
193 note that the write-port wen, write-port data, and go_wr_i all need to
194 be on the exact same clock cycle. as there is a combinatorial loop bug
195 at the moment, these all use sync.
197 comb
, sync
= m
.d
.comb
, m
.d
.sync
200 # dictionary of lists of regfile write ports
201 byregfiles_wr
, byregfiles_wrspec
= self
.get_byregfiles(False)
203 # same for write ports.
204 # BLECH! complex code-duplication! BLECH!
206 for regfile
, spec
in byregfiles_wr
.items():
207 fuspecs
= byregfiles_wrspec
[regfile
]
208 wrpickers
[regfile
] = {}
209 for (regname
, fspec
) in sort_fuspecs(fuspecs
):
210 print ("connect wr", regname
, fspec
)
212 # get the regfile specs for this regfile port
213 (rf
, read
, write
, wid
, fuspec
) = fspec
215 # select the required write port. these are pre-defined sizes
216 print (regfile
, regs
.rf
.keys())
217 wport
= regs
.rf
[regfile
.lower()].w_ports
[rpidx
]
219 # create a priority picker to manage this port
220 wrpickers
[regfile
][rpidx
] = wrpick
= PriorityPicker(len(fuspec
))
221 setattr(m
.submodules
, "wrpick_%s_%s" % (regfile
, rpidx
), wrpick
)
223 # connect the regspec write "reg select" number to this port
224 # only if one FU actually requests (and is granted) the port
225 # will the write-enable be activated
226 with m
.If(wrpick
.en_o
):
227 sync
+= wport
.wen
.eq(write
)
229 sync
+= wport
.wen
.eq(0)
231 # connect up the FU req/go signals and the reg-read to the FU
232 # these are arbitrated by Data.ok signals
234 for pi
, (funame
, fu
, idx
) in enumerate(fuspec
):
235 # write-request comes from dest.ok
236 dest
= fu
.get_out(idx
)
237 name
= "wrflag_%s_%s_%d" % (funame
, regname
, idx
)
238 wrflag
= Signal(name
=name
, reset_less
=True)
239 comb
+= wrflag
.eq(dest
.ok
)
241 # connect request-read to picker input, and output to go-wr
242 fu_active
= fu_bitdict
[funame
]
243 pick
= fu
.wr
.rel
[idx
] & fu_active
#& wrflag
244 comb
+= wrpick
.i
[pi
].eq(pick
)
245 sync
+= fu
.go_wr_i
[idx
].eq(wrpick
.o
[pi
] & wrpick
.en_o
)
246 # connect regfile port to input
247 print ("reg connect widths",
248 regfile
, regname
, pi
, funame
,
249 dest
.shape(), wport
.data_i
.shape())
252 # here is where we create the Write Broadcast Bus. simple, eh?
253 sync
+= wport
.data_i
.eq(ortreereduce(wsigs
, "data"))
255 def get_byregfiles(self
, readmode
):
257 mode
= "read" if readmode
else "write"
262 # dictionary of lists of regfile ports
265 for (funame
, fu
) in fus
.items():
266 print ("%s ports for %s" % (mode
, funame
))
267 for idx
in range(fu
.n_src
if readmode
else fu
.n_dst
):
269 (regfile
, regname
, wid
) = fu
.get_in_spec(idx
)
271 (regfile
, regname
, wid
) = fu
.get_out_spec(idx
)
272 print (" %d %s %s %s" % (idx
, regfile
, regname
, str(wid
)))
274 rdflag
, read
= dec2
.regspecmap_read(regfile
, regname
)
277 rdflag
, read
= None, None
278 wrport
, write
= dec2
.regspecmap_write(regfile
, regname
)
279 if regfile
not in byregfiles
:
280 byregfiles
[regfile
] = {}
281 byregfiles_spec
[regfile
] = {}
282 if regname
not in byregfiles_spec
[regfile
]:
283 byregfiles_spec
[regfile
][regname
] = \
284 [rdflag
, read
, write
, wid
, []]
285 # here we start to create "lanes"
286 if idx
not in byregfiles
[regfile
]:
287 byregfiles
[regfile
][idx
] = []
288 fuspec
= (funame
, fu
, idx
)
289 byregfiles
[regfile
][idx
].append(fuspec
)
290 byregfiles_spec
[regfile
][regname
][4].append(fuspec
)
292 # ok just print that out, for convenience
293 for regfile
, spec
in byregfiles
.items():
294 print ("regfile %s ports:" % mode
, regfile
)
295 fuspecs
= byregfiles_spec
[regfile
]
296 for regname
, fspec
in fuspecs
.items():
297 [rdflag
, read
, write
, wid
, fuspec
] = fspec
298 print (" rf %s port %s lane: %s" % (mode
, regfile
, regname
))
299 print (" %s" % regname
, wid
, read
, write
, rdflag
)
300 for (funame
, fu
, idx
) in fuspec
:
301 fusig
= fu
.src_i
[idx
] if readmode
else fu
.dest
[idx
]
302 print (" ", funame
, fu
, idx
, fusig
)
305 return byregfiles
, byregfiles_spec
308 yield from self
.fus
.ports()
309 yield from self
.pdecode2
.ports()
316 class TestIssuer(Elaboratable
):
317 """TestIssuer - reads instructions from TestMemory and issues them
319 efficiency and speed is not the main goal here: functional correctness is.
321 def __init__(self
, addrwid
=6, idepth
=16):
322 # main instruction core
323 self
.core
= core
= NonProductionCore(addrwid
)
325 # Test Instruction memory
326 self
.imem
= TestMemory(32, idepth
)
327 self
.i_rd
= self
.imem
.read_port()
328 #self.i_wr = self.imem.write_port() errr...
330 # instruction go/monitor
331 self
.go_insn_i
= Signal(reset_less
=True)
332 self
.pc_o
= Signal(64, reset_less
=True)
333 self
.pc_i
= Data(64, "pc") # set "ok" to indicate "please change me"
334 self
.busy_o
= core
.busy_o
336 def elaborate(self
, platform
):
339 m
.submodules
.core
= core
= self
.core
340 m
.submodules
.imem
= imem
= self
.imem
342 current_pc
= Signal(64, reset_less
=True)
345 if __name__
== '__main__':
346 dut
= NonProductionCore()
347 vl
= rtlil
.convert(dut
, ports
=dut
.ports())
348 with
open("non_production_core.il", "w") as f
: