File size: 7,517 Bytes
0ba037b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#!/usr/bin/env python3
#
# LiteX SoC for the LiteFury (Xilinx Artix-7 XC7A100T, PCIe Gen2 x4, M.2 2280 Key-M).
# LiteX supports it as the SQRL Acorn CLE-101, its electrical equivalent
# (litex_boards/platforms/sqrl_acorn.py).
#
# The bit-operation engine (pcie/rtl) is attached to one LitePCIe DMA channel:
#   DMA reader (host -> card) -> 128-bit descriptors -> engine
#   engine -> 128-bit result beats (2 results of 64 bits) -> DMA writer (card -> host)
# Descriptor and result formats: pcie/rtl/bitacc_pcie_core.sv (format version 2,
# readable from the version CSR).
# Word access to the engine's bit store and status counters are CSRs on BAR0.
#
# Build (needs Vivado for the bitstream; the free edition covers the XC7A100T):
#   ./bitacc_litefury.py --build                 # gateware + bitstream
#   ./bitacc_litefury.py --driver                # Linux driver + liblitepcie in build/.../driver
# Generate sources only (no Vivado):
#   ./bitacc_litefury.py --no-compile
#
# The same SoC serves the M.2 slot of the NUC directly or a USB4/Thunderbolt
# enclosure that tunnels PCIe; nothing here depends on the transport.

import os

from migen import *

from litex.gen import *
from litex.soc.interconnect.csr import *
from litex.soc.integration.builder import Builder
from litex.build.generic_platform import Pins

from litex_boards.targets.sqrl_acorn import BaseSoC

FORMAT_VERSION = 3

RTL = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "rtl")


class BitAcc(LiteXModule):
    """bitacc_pcie_core with CSR access; connect dma_source/dma_sink to a LitePCIe DMA."""

    def __init__(self, platform, dma, lanes=8, words_per_lane=2048, with_match=True, match_lanes=2):
        word_bits = (lanes * words_per_lane - 1).bit_length()

        # Host word access.
        self.host_word  = CSRStorage(word_bits, description="Bit-store word index for host access.")
        self.host_wdata = CSRStorage(64,        description="Word to write.")
        self.host_ctrl  = CSRStorage(fields=[
            CSRField("write", size=1, offset=0, pulse=True, description="Write host_wdata to host_word."),
            CSRField("read",  size=1, offset=1, pulse=True, description="Read host_word into host_rdata."),
        ])
        self.host_rdata = CSRStatus(64, description="Word read by the last read.")
        self.status     = CSRStatus(fields=[
            CSRField("idle",        size=1, offset=0, description="No operation in flight, no result buffered."),
            CSRField("host_busy",   size=1, offset=1, description="A host word access is pending."),
            CSRField("rdata_valid", size=1, offset=2, description="host_rdata holds the last read."),
        ])
        self.ops_accepted = CSRStatus(32, description="Descriptors accepted since reset.")
        self.results_sent = CSRStatus(32, description="Results sent since reset (multiple of 2).")
        self.config       = CSRStatus(fields=[
            CSRField("lanes",          size=8,  offset=0),
            CSRField("words_per_lane", size=24, offset=8),
        ])
        self.version      = CSRStatus(8, reset=FORMAT_VERSION,
            description="Descriptor/result format version (see bitacc_pcie_core.sv).")
        self.features     = CSRStatus(fields=[
            CSRField("match", size=1, offset=0, reset=int(with_match), description="MATCH unit present."),
            CSRField("match_lanes", size=8, offset=8, reset=match_lanes if with_match else 0,
                     description="MATCH units (64 positions per cycle each)."),
        ])
        self.comb += [
            self.config.fields.lanes.eq(lanes),
            self.config.fields.words_per_lane.eq(words_per_lane),
        ]

        self.specials += Instance("bitacc_pcie_core",
            p_LANES          = lanes,
            p_WORDS_PER_LANE = words_per_lane,
            p_WITH_MATCH     = int(with_match),
            p_MATCH_LANES    = match_lanes,
            i_clk            = ClockSignal("sys"),
            i_reset          = ResetSignal("sys"),
            # host -> card
            i_in_valid       = dma.source.valid,
            o_in_ready       = dma.source.ready,
            i_in_data        = dma.source.data,
            # card -> host
            o_out_valid      = dma.sink.valid,
            i_out_ready      = dma.sink.ready,
            o_out_data       = dma.sink.data,
            # registers
            i_host_word      = self.host_word.storage,
            i_host_wdata     = self.host_wdata.storage,
            i_host_write_stb = self.host_ctrl.fields.write,
            i_host_read_stb  = self.host_ctrl.fields.read,
            o_host_rdata     = self.host_rdata.status,
            o_host_rdata_valid = self.status.fields.rdata_valid,
            o_host_busy      = self.status.fields.host_busy,
            o_idle           = self.status.fields.idle,
            o_ops_accepted   = self.ops_accepted.status,
            o_results_sent   = self.results_sent.status,
        )
        self.comb += dma.sink.last.eq(0)
        for f in ("bitacc_engine.sv", "bitacc_pcie_core.sv"):
            platform.add_source(os.path.join(RTL, f))


class BitAccSoC(BaseSoC):
    def __init__(self, lanes=8, words_per_lane=2048, with_match=False, match_lanes=2, **kwargs):
        BaseSoC.__init__(self,
            variant            = "cle-101",     # LiteFury-equivalent XC7A100T
            with_pcie          = True,
            pcie_ndmas         = 1,
            **kwargs)
        self.bitacc = BitAcc(self.platform, self.pcie_dma0,
            lanes=lanes, words_per_lane=words_per_lane, with_match=with_match, match_lanes=match_lanes)


def main():
    from litex.build.parser import LiteXArgumentParser
    from litex_boards.platforms import sqrl_acorn
    parser = LiteXArgumentParser(platform=sqrl_acorn.Platform, description="Bit accelerator on LiteFury.")
    parser.add_target_argument("--sys-clk-freq",   default=125e6, type=float, help="System clock frequency.")
    parser.add_target_argument("--lanes",          default=8,     type=int,   help="Engine lanes (power of two).")
    parser.add_target_argument("--words-per-lane", default=2048,  type=int,   help="64-bit words per lane (power of two).")
    # MATCH at 8 lanes uses ~65% of the XC7A100T's LUTs and did not route in the
    # open-source flow (pcie/README.md, Resources), so it is opt-in.
    parser.add_target_argument("--match",          action="store_true",       help="Include the MATCH unit.")
    parser.add_target_argument("--match-lanes",    default=2,     type=int,   help="MATCH units (power of two, <= lanes; ~4,000 LUTs each).")
    parser.add_target_argument("--driver",         action="store_true",       help="Generate the PCIe driver.")
    # The host drives the engine over PCIe; no soft CPU or UART is needed.
    parser.set_defaults(cpu_type="None", no_uart=True)
    args = parser.parse_args()

    soc = BitAccSoC(
        sys_clk_freq   = args.sys_clk_freq,
        lanes          = args.lanes,
        words_per_lane = args.words_per_lane,
        with_match     = args.match,
        match_lanes    = args.match_lanes,
        **parser.soc_argdict)
    builder = Builder(soc, **parser.builder_argdict)
    if args.build:
        builder.build(**parser.toolchain_argdict)
    else:
        builder.build(run=False)
    if args.driver:
        from litepcie.software import generate_litepcie_software
        generate_litepcie_software(soc, os.path.join(builder.output_dir, "driver"))


if __name__ == "__main__":
    main()