accel_glue.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. # Copyright (c) 2025 Victor Suarez Rovere <suarezvictor@gmail.com>
  2. # SPDX-License-Identifier: AGPL-3.0-only
  3. # code portions from LiteX framework (C) Enjoy-Digital https://github.com/enjoy-digital/litex
  4. from migen import *
  5. from litex.soc.interconnect import wishbone
  6. from litedram.frontend.wishbone import LiteDRAMWishbone2Native
  7. #adapts Wishbone slave to native slave
  8. def wb_to_native_adapter(wb_mbus, native_port):
  9. litedram_wb = wishbone.Interface(data_width=native_port.data_width)
  10. wb2native = LiteDRAMWishbone2Native(
  11. wishbone = wb_mbus,
  12. port = native_port,
  13. base_address = 0)
  14. return wb2native
  15. #this Cache implementation is based on https://github.com/enjoy-digital/litex/blob/master/litex/soc/interconnect/wishbone.py#L553
  16. from migen.fhdl.bitcontainer import log2_int
  17. from migen.genlib.misc import split, displacer, chooser
  18. from migen.genlib.fsm import FSM, NextState, NextValue
  19. from migen.genlib.record import layout_len
  20. from migen.fhdl.specials import Memory
  21. class WriteBackCache(Module):
  22. """Cache
  23. This module is a write-back wishbone cache that can be used as a L2 cache.
  24. Cachesize (in 32-bit words) is the size of the data store and must be a power of 2
  25. """
  26. def __init__(self, cachesize, master, slave, reverse=True, skip_reads=False, debug=False):
  27. self.master = master
  28. self.slave = slave
  29. dw_from = len(master.dat_r)
  30. dw_to = len(slave.dat_r)
  31. if dw_to > dw_from and (dw_to % dw_from) != 0:
  32. raise ValueError("Slave data width must be a multiple of {dw}".format(dw=dw_from))
  33. if dw_to < dw_from and (dw_from % dw_to) != 0:
  34. raise ValueError("Master data width must be a multiple of {dw}".format(dw=dw_to))
  35. # Split address:
  36. # TAG | LINE NUMBER | LINE OFFSET
  37. offsetbits = log2_int(max(dw_to//dw_from, 1))
  38. addressbits = len(slave.adr) + offsetbits
  39. linebits = log2_int(cachesize) - offsetbits
  40. tagbits = addressbits - linebits
  41. wordbits = log2_int(max(dw_from//dw_to, 1))
  42. adr_offset, adr_line, adr_tag = split(master.adr, offsetbits, linebits, tagbits)
  43. word = Signal(wordbits) if wordbits else None
  44. # Data memory
  45. data_mem = Memory(dw_to*2**wordbits, 2**linebits)
  46. data_port = data_mem.get_port(write_capable=True, we_granularity=8)
  47. self.specials += data_mem, data_port
  48. # Byte selection memory
  49. sel_mem = Memory(len(slave.sel), 2**linebits)
  50. sel_port = sel_mem.get_port(write_capable=True)
  51. self.specials += sel_mem, sel_port
  52. write_from_slave = Signal()
  53. if adr_offset is None:
  54. adr_offset_r = None
  55. else:
  56. adr_offset_r = Signal(offsetbits, reset_less=True)
  57. self.sync += adr_offset_r.eq(adr_offset)
  58. # Tag memory
  59. tag_layout = [("tag", tagbits), ("dirty", 1)] #TODO: dirty could track the selected bits
  60. tag_mem = Memory(layout_len(tag_layout), 2**linebits)
  61. tag_port = tag_mem.get_port(write_capable=True)
  62. self.specials += tag_mem, tag_port
  63. tag_do = Record(tag_layout)
  64. tag_di = Record(tag_layout)
  65. self.comb += [
  66. tag_do.raw_bits().eq(tag_port.dat_r),
  67. tag_port.dat_w.eq(tag_di.raw_bits())
  68. ]
  69. self.comb += [
  70. tag_port.adr.eq(adr_line),
  71. tag_di.tag.eq(adr_tag)
  72. ]
  73. # slave word computation, word_clr and word_inc will be simplified
  74. # at synthesis when wordbits=0
  75. word_clr = Signal()
  76. word_inc = Signal()
  77. if word is not None:
  78. self.sync += \
  79. If(word_clr,
  80. word.eq(0),
  81. ).Elif(word_inc,
  82. word.eq(word+1)
  83. )
  84. # Data & selection memory logic
  85. def word_is_last(word):
  86. if word is not None:
  87. return word == 2**wordbits-1
  88. else:
  89. return 1
  90. self.comb += [
  91. data_port.adr.eq(adr_line),
  92. sel_port.adr.eq(adr_line),
  93. sel_port.we.eq(0), #TODO: needed?
  94. If(write_from_slave,
  95. displacer(slave.dat_r, word, data_port.dat_w),
  96. displacer(Replicate(1, dw_to//8), word, data_port.we),
  97. ).Else(
  98. data_port.dat_w.eq(Replicate(master.dat_w, max(dw_to//dw_from, 1))),
  99. If(master.cyc & master.stb & master.we & master.ack, #write from master
  100. displacer(master.sel, adr_offset, data_port.we, 2**offsetbits, reverse=reverse),
  101. )
  102. ),
  103. chooser(data_port.dat_r, word, slave.dat_w),
  104. chooser(data_port.dat_r, adr_offset_r, master.dat_r, reverse=reverse)
  105. ]
  106. first_state = "TEST_HIT" if skip_reads else "REFILL"
  107. autoevict_counter = Signal(len(adr_line))
  108. self.submodules.fsm = fsm = FSM(reset_state="TEST_HIT")
  109. fsm.act("IDLE", #IDLE state not needed, logic moved to TEST_HIT state
  110. If(master.cyc,
  111. NextState("TEST_HIT")
  112. ).Else
  113. (
  114. adr_line.eq(autoevict_counter), tag_port.adr.eq(adr_line), data_port.adr.eq(adr_line), sel_port.adr.eq(adr_line), #TODO: needed?
  115. NextState("AUTO_EVICT")
  116. )
  117. )
  118. fsm.act("TEST_HIT",
  119. If(master.cyc & master.stb,
  120. word_clr.eq(1),
  121. NextValue(autoevict_counter, adr_tag^(2**(linebits-1))), #this is to avoid trying to automatically evict current location
  122. If(tag_do.tag == adr_tag,
  123. master.ack.eq(1),
  124. If(master.we,
  125. tag_di.dirty.eq(1),
  126. tag_port.we.eq(1), sel_port.we.eq(1), sel_port.dat_w.eq(master.sel | sel_port.dat_r), #selection bits are ORed each time
  127. ),
  128. ).Else(
  129. If(tag_do.dirty,
  130. NextState("EVICT")
  131. ).Else(
  132. # Write the tag first to set the slave address
  133. tag_port.we.eq(1), sel_port.we.eq(1), If(master.we, sel_port.dat_w.eq(master.sel)).Else(sel_port.dat_w.eq(0)),
  134. word_clr.eq(1),
  135. NextState(first_state)
  136. )
  137. )
  138. ).Elif(~master.cyc,
  139. NextState("IDLE")
  140. )
  141. )
  142. fsm.act("EVICT",
  143. slave.stb.eq(1),
  144. slave.cyc.eq(1),
  145. slave.we.eq(1),
  146. If(slave.ack,
  147. word_inc.eq(1),
  148. If(word_is_last(word),
  149. # Write the tag first to set the slave address
  150. tag_port.we.eq(1), sel_port.we.eq(1), sel_port.dat_w.eq(0),
  151. word_clr.eq(1),
  152. NextState(first_state)
  153. )
  154. )
  155. )
  156. auto_evict = Signal()
  157. fsm.act("AUTO_EVICT",
  158. adr_line.eq(autoevict_counter), tag_port.adr.eq(adr_line), data_port.adr.eq(adr_line), sel_port.adr.eq(adr_line),
  159. auto_evict.eq(tag_do.dirty),
  160. If(auto_evict,
  161. slave.cyc.eq(1),
  162. slave.stb.eq(1),
  163. slave.we.eq(1),
  164. If(slave.ack,
  165. tag_di.tag.eq(tag_do.tag), #keep tag
  166. tag_di.dirty.eq(0), #except dirty
  167. tag_port.we.eq(1), sel_port.we.eq(1), sel_port.dat_w.eq(0),
  168. NextValue(autoevict_counter, autoevict_counter+1),
  169. NextState("IDLE")
  170. ),
  171. ).Else(NextState("IDLE"))
  172. )
  173. if debug:
  174. xtag = Signal(tagbits)
  175. xtag.eq(tag_do.tag)
  176. adr = Signal(len(slave.adr))
  177. adr.eq(Cat(adr_line, tag_do.tag))
  178. self.sync += If(auto_evict,
  179. Display("AUTO_EVICT line %d, tag 0x%08X, addr 0x%08X, sel 0x%04X, data 0x%032X", autoevict_counter, xtag, adr, sel_port.dat_r, data_port.dat_r)
  180. )
  181. fsm.act("REFILL", #TODO: avoid refill if only writings
  182. slave.stb.eq(1),
  183. slave.cyc.eq(1),
  184. slave.we.eq(0),
  185. If(slave.ack,
  186. write_from_slave.eq(1),
  187. word_inc.eq(1),
  188. If(word_is_last(word),
  189. NextState("TEST_HIT"),
  190. ).Else(
  191. NextState(first_state)
  192. )
  193. )
  194. )
  195. if word is not None:
  196. self.comb += slave.adr.eq(Cat(word, adr_line, tag_do.tag))
  197. else:
  198. self.comb += slave.adr.eq(Cat(adr_line, tag_do.tag))
  199. self.comb += slave.sel.eq(sel_port.dat_r)
  200. class ConverterWriteCache(Module):
  201. def __init__(self, master, slave, write_back=True, wb_skip_reads=True, debug=False):
  202. assert(master.data_width == slave.data_width) #if it works with different sizes should be tested
  203. if debug:
  204. self.sync += If(slave.cyc & slave.stb & slave.we,
  205. Display("DST WRITE addr 0x%08X (data 0x%032X, sel 0x%04X) ack %d", slave.adr, slave.dat_w, slave.sel, slave.ack))
  206. self.sync += If(slave.cyc & slave.stb & ~slave.we,
  207. Display("DST READ addr 0x%08X (data 0x%032X) ack %d", slave.adr, slave.dat_r, slave.ack))
  208. self.sync += If(master.cyc & master.stb & master.we,
  209. Display("SRC WRITE addr 0x%08X (data 0x%032X, sel 0x%04X) ack %d", master.adr, master.dat_w, master.sel, master.ack))
  210. self.sync += If(master.cyc & master.stb & ~master.we,
  211. Display("SRC READ addr 0x%08X (data 0x%032X) ack %d", master.adr, master.dat_r, master.ack))
  212. slave_tmp = wishbone.Interface(data_width=slave.data_width)
  213. if write_back:
  214. self.submodules.cache = WriteBackCache(32, master=master, slave=slave_tmp, reverse=False, skip_reads=wb_skip_reads)
  215. else:
  216. self.submodules.cache = wishbone.Cache(32, master=master, slave=slave_tmp, reverse=False)
  217. self.comb += slave_tmp.connect(slave) #new usage is master.connect(slave)
  218. def connect_accel_to_native_wbcache(wpu, port):
  219. bus = wishbone.Interface(port.data_width)
  220. busx = wishbone.Interface(port.data_width)
  221. dma_bus = wpu.dma_bus
  222. wb_cnv = wishbone.Converter(master=dma_bus, slave=busx) #adapts width prior to cache
  223. wpu.submodules.wb_cnv = wb_cnv
  224. cache = ConverterWriteCache(busx, bus, write_back=True, wb_skip_reads=True)
  225. wpu.submodules.cache = cache
  226. s1 = wb_to_native_adapter(bus, port)
  227. wpu.submodules += s1
  228. def connect_accel_wbcache(wpu):
  229. dma_bus= wpu.dma_bus
  230. bus = wishbone.Interface(dma_bus.data_width)
  231. cache = ConverterWriteCache(dma_bus, bus)
  232. wpu.submodules.cache = cache
  233. return bus
  234. def gen_accel_cores(soc, active_cores, pixel_bus_width=32):
  235. for core in active_cores:
  236. corename = "accel_" + core
  237. fb_offset = 0xC00000
  238. #direct instancing
  239. from wpu import WPUBase
  240. wpu = WPUBase(corename)
  241. setattr(soc.submodules, corename, wpu)
  242. vram_origin = soc.bus.regions["main_ram"].origin # usually 0x40000000
  243. soc.add_constant("VRAM_ORIGIN_"+corename, vram_origin + fb_offset)
  244. soc.platform.add_source(f"{core}.v")
  245. region_name = corename+"_region" #CSR base
  246. region = soc.bus.alloc_region(region_name, 0x1000, cached=False)
  247. soc.add_constant(region_name, region.origin)
  248. #benchmark results are for 1366x768 resolution (Arty platform)
  249. if True:
  250. #with write_back cache and 128-bit native: FPS 15 ticks 6563003, clocks per pixel 6
  251. #with standard cache and 128-bit native: FPS 8 ticks 11898854, clocks per pixel 11
  252. connect_accel_to_native_wbcache(wpu, soc.sdram.crossbar.get_port(mode="both", data_width=128))
  253. if False:
  254. #direct to 32-bit wishbone: FPS 8 ticks 12306127, clocks per pixel 11
  255. wpu.connect_to_soc(soc)
  256. if False:
  257. #write-back cache for 32-bit wishbone: FPS 8 ticks 12306510, clocks per pixel 11
  258. bus = connect_accel_wbcache(wpu)
  259. soc.bus.add_master(master=wpu.dma_bus, name="dma_bus_"+wpu.name)