Download pcie/host/bitacc_pcie.c from Snapkitty/bit-string-accelerator: direct link, hf CLI and curl.
- Browser
- Download file 23.6 kB
-
https://huggingface.co/Snapkitty/bit-string-accelerator/resolve/main/pcie/host/bitacc_pcie.c
- Command line
-
hf download hf://Snapkitty/bit-string-accelerator/pcie/host/bitacc_pcie.c
-
curl -L -o bitacc_pcie.c https://huggingface.co/Snapkitty/bit-string-accelerator/resolve/main/pcie/host/bitacc_pcie.c
23.6 kB
| /* | |
| * bitacc_pcie: host program for the bit accelerator on a LitePCIe card | |
| * (pcie/litex/bitacc_litefury.py). Built against the driver LiteX generates: | |
| * | |
| * make LITEPCIE=<build>/driver | |
| * | |
| * bitacc_pcie [-c /dev/litepcie0] info | |
| * bitacc_pcie [-c /dev/litepcie0] selftest [operations] [seed] [range-percent] | |
| * bitacc_pcie [-c /dev/litepcie0] bench get|count|find|xor|match [descriptors] | |
| * | |
| * selftest loads random data into the whole bit store (CSRs), streams random | |
| * descriptors (single-bit and range operations) through the DMA reader, checks | |
| * every result returned by the DMA writer against a model of the engine, then | |
| * reads the bit store back and compares it. It prints the descriptor | |
| * throughput of the DMA phase. | |
| * | |
| * bench streams one kind of descriptor and prints descriptors/s and the bits | |
| * of the bit store they cover per second: | |
| * get random single-bit GETs | |
| * count COUNT over the whole bit store | |
| * find FIND1 over the whole bit store, which bench clears first (not found) | |
| * xor BULK XOR, dry run, of the lower half of the store with the upper half | |
| * match MATCH (count) of a 16-bit pattern over the whole bit store | |
| * | |
| * Descriptor (16 bytes, little-endian), format version 3: | |
| * [63:0] a [67:64] op [68] dry [71:69] fn [103:72] len [127:104] src | |
| * Result (8 bytes, little-endian): [7:0] 0xA0 | error << 1 | bit, [63:8] value. | |
| * Semantics: pcie/rtl/bitacc_engine.sv. The DMA moves DMA_BUFFER_SIZE bytes | |
| * per buffer, so a run is padded with no-op descriptors (opcode 7: no write, | |
| * result "error") to whole buffers in both directions. | |
| */ | |
| /* A run fills whole buffers in both directions. */ | |
| enum { OP_GET, OP_TEST, OP_SET, OP_CLEAR, OP_TOGGLE, OP_NOP = 7, | |
| OP_COUNT = 8, OP_FIND1, OP_FIND0, OP_SETR, OP_CLEARR, OP_FLIPR, OP_BULK, OP_MATCH }; | |
| enum { FN_COPY, FN_AND, FN_OR, FN_XOR, FN_ANDN }; | |
| struct desc { | |
| uint64_t a; /* bit address, or dst word (BULK) */ | |
| uint8_t op, fn, dry; | |
| uint32_t len; /* bits, or words (BULK) */ | |
| uint32_t src; /* src word (BULK), pattern word (MATCH; mask in src + 1), 24 bits */ | |
| }; | |
| static const char *device = "/dev/litepcie0"; | |
| static int has_match; /* gateware has the MATCH unit (features CSR) */ | |
| /* ---------------------------------------------------------------- CSRs */ | |
| static uint32_t rd(int fd, uint32_t a) { return litepcie_readl(fd, a); } | |
| static void wr(int fd, uint32_t a, uint32_t v) { litepcie_writel(fd, a, v); } | |
| static uint32_t status(int fd) { return rd(fd, CSR_BITACC_STATUS_ADDR); } | |
| static int wait_status(int fd, uint32_t mask, uint32_t want) | |
| { | |
| for (int i = 0; i < 1000000; i++) | |
| if ((status(fd) & mask) == want) | |
| return 0; | |
| fprintf(stderr, "bitacc: timeout waiting for status 0x%x (status 0x%x)\n", want, status(fd)); | |
| return -1; | |
| } | |
| /* 64-bit CSRs: most significant word at the lower address (CSR_ORDERING_BIG). */ | |
| static int word_write(int fd, uint32_t index, uint64_t v) | |
| { | |
| wr(fd, CSR_BITACC_HOST_WORD_ADDR, index); | |
| wr(fd, CSR_BITACC_HOST_WDATA_ADDR, (uint32_t)(v >> 32)); | |
| wr(fd, CSR_BITACC_HOST_WDATA_ADDR + 4, (uint32_t)v); | |
| wr(fd, CSR_BITACC_HOST_CTRL_ADDR, 1u << CSR_BITACC_HOST_CTRL_WRITE_OFFSET); | |
| return wait_status(fd, ST_HOST_BUSY, 0); | |
| } | |
| static int word_read(int fd, uint32_t index, uint64_t *v) | |
| { | |
| wr(fd, CSR_BITACC_HOST_WORD_ADDR, index); | |
| wr(fd, CSR_BITACC_HOST_CTRL_ADDR, 1u << CSR_BITACC_HOST_CTRL_READ_OFFSET); | |
| if (wait_status(fd, ST_HOST_BUSY | ST_RDATA_VALID, ST_RDATA_VALID)) | |
| return -1; | |
| *v = ((uint64_t)rd(fd, CSR_BITACC_HOST_RDATA_ADDR) << 32) | rd(fd, CSR_BITACC_HOST_RDATA_ADDR + 4); | |
| return 0; | |
| } | |
| static void geometry(int fd, uint32_t *lanes, uint32_t *wpl) | |
| { | |
| uint32_t c = rd(fd, CSR_BITACC_CONFIG_ADDR); | |
| *lanes = (c >> CSR_BITACC_CONFIG_LANES_OFFSET) & 0xff; | |
| *wpl = c >> CSR_BITACC_CONFIG_WORDS_PER_LANE_OFFSET; | |
| } | |
| static int check_version(int fd) | |
| { | |
| uint32_t v = rd(fd, CSR_BITACC_VERSION_ADDR); | |
| if (v != FORMAT_VERSION) { | |
| fprintf(stderr, "bitacc: gateware descriptor format %u, this program speaks %u\n", v, FORMAT_VERSION); | |
| return -1; | |
| } | |
| has_match = (rd(fd, CSR_BITACC_FEATURES_ADDR) >> CSR_BITACC_FEATURES_MATCH_OFFSET) & 1; | |
| return 0; | |
| } | |
| /* ---------------------------------------------------------------- descriptors */ | |
| static void encode(uint8_t *d, const struct desc *x) | |
| { | |
| for (int i = 0; i < 8; i++) d[i] = (uint8_t)(x->a >> (8 * i)); | |
| d[8] = (uint8_t)((x->op & 15) | (x->dry & 1) << 4 | (x->fn & 7) << 5); | |
| for (int i = 0; i < 4; i++) d[9 + i] = (uint8_t)(x->len >> (8 * i)); | |
| for (int i = 0; i < 3; i++) d[13 + i] = (uint8_t)(x->src >> (8 * i)); | |
| } | |
| static uint64_t decode_result(const char *p) | |
| { | |
| uint64_t r = 0; | |
| for (int i = 0; i < 8; i++) r |= (uint64_t)(uint8_t)p[i] << (8 * i); | |
| return r; | |
| } | |
| /* ---------------------------------------------------------------- model */ | |
| /* | |
| * Word-level model of pcie/rtl/bitacc_engine.sv: returns the expected 64-bit | |
| * result and applies the operation to mem. It is written independently of | |
| * the RTL testbenches, which expand range operations bit by bit on the | |
| * reference core. | |
| */ | |
| static uint64_t result(int bit, uint64_t value) { return 0xA0ull | (uint64_t)(bit != 0) | value << 8; } | |
| static uint64_t model(uint64_t *mem, uint64_t words, const struct desc *x) | |
| { | |
| uint64_t bits = words * 64; | |
| if (x->op <= OP_TOGGLE) { | |
| uint64_t w = x->a >> 6; | |
| unsigned b = x->a & 63; | |
| if (w >= words) | |
| return RESULT_ERROR; | |
| uint64_t m = 1ull << b, old = (mem[w] >> b) & 1, nb; | |
| switch (x->op) { | |
| case OP_SET: mem[w] |= m; nb = 1; break; | |
| case OP_CLEAR: mem[w] &= ~m; nb = 0; break; | |
| case OP_TOGGLE: mem[w] ^= m; nb = !old; break; | |
| default: nb = old; break; | |
| } | |
| return result((int)nb, 0); | |
| } | |
| if (x->op >= OP_COUNT && x->op <= OP_FLIPR) { | |
| if (x->a > bits || x->len > bits - x->a) | |
| return RESULT_ERROR; | |
| int find = x->op == OP_FIND1 || x->op == OP_FIND0, found = 0; | |
| uint64_t cnt = 0, idx = 0, end = x->a + x->len; | |
| for (uint64_t b = x->a; b < end && !found; ) { | |
| uint64_t w = b >> 6; | |
| unsigned lo = b & 63; | |
| uint64_t n = 64 - lo < end - b ? 64 - lo : end - b; | |
| uint64_t m = (n == 64 ? ~0ull : (1ull << n) - 1) << lo; | |
| uint64_t v = mem[w], hit; | |
| switch (x->op) { | |
| case OP_COUNT: cnt += (uint64_t)__builtin_popcountll(v & m); break; | |
| case OP_FIND1: | |
| case OP_FIND0: | |
| hit = (x->op == OP_FIND1 ? v : ~v) & m; | |
| if (hit) { found = 1; idx = w * 64 + (uint64_t)__builtin_ctzll(hit); } | |
| break; | |
| case OP_SETR: cnt += (uint64_t)__builtin_popcountll(v & m); mem[w] = v | m; break; | |
| case OP_CLEARR: cnt += (uint64_t)__builtin_popcountll(v & m); mem[w] = v & ~m; break; | |
| default: cnt += (uint64_t)__builtin_popcountll(v & m); mem[w] = v ^ m; break; | |
| } | |
| b += n; | |
| } | |
| return find ? result(found, found ? idx : 0) : result(cnt != 0, cnt); | |
| } | |
| if (x->op == OP_MATCH) { | |
| uint64_t a = x->a, n = x->len, p = x->src; | |
| if (!has_match || x->fn > 1 || a > bits || n > bits - a || p + 2 > words) | |
| return RESULT_ERROR; | |
| uint64_t pat = mem[p], msk = mem[p + 1]; | |
| /* position s is valid if every mask bit lies inside the haystack */ | |
| uint64_t top = msk ? 63 - (uint64_t)__builtin_clzll(msk) : 0, cnt = 0, first = 0; | |
| int found = 0; | |
| for (uint64_t s = a; s < a + n && s + top < a + n; s++) { | |
| uint64_t w = s >> 6, o = s & 63; | |
| uint64_t lo = mem[w], hi = w + 1 < words ? mem[w + 1] : 0; | |
| uint64_t win = o ? (lo >> o) | (hi << (64 - o)) : lo; | |
| if (((win ^ pat) & msk) == 0) { | |
| cnt++; | |
| if (!found) { found = 1; first = s; } | |
| } | |
| } | |
| return x->fn ? result(found, first) : result(cnt != 0, cnt); | |
| } | |
| if (x->op == OP_BULK) { | |
| uint64_t d = x->a, s = x->src, n = x->len; | |
| if (x->fn > FN_ANDN || d > words || n > words - d || s > words || n > words - s) | |
| return RESULT_ERROR; | |
| if (n && d != s && d < s + n && s < d + n) | |
| return RESULT_ERROR; /* overlapping, not identical */ | |
| uint64_t cnt = 0; | |
| for (uint64_t k = 0; k < n; k++) { | |
| uint64_t a = mem[d + k], b = mem[s + k], r; | |
| switch (x->fn) { | |
| case FN_COPY: r = b; break; | |
| case FN_AND: r = a & b; break; | |
| case FN_OR: r = a | b; break; | |
| case FN_XOR: r = a ^ b; break; | |
| default: r = a & ~b; break; | |
| } | |
| cnt += (uint64_t)__builtin_popcountll(r); | |
| if (!x->dry) mem[d + k] = r; | |
| } | |
| return result(cnt != 0, cnt); | |
| } | |
| return RESULT_ERROR; /* 5-7 */ | |
| } | |
| static uint64_t rng_state; | |
| static uint64_t rng(void) | |
| { | |
| rng_state ^= rng_state << 13; | |
| rng_state ^= rng_state >> 7; | |
| rng_state ^= rng_state << 17; | |
| return rng_state; | |
| } | |
| static uint64_t umin(uint64_t a, uint64_t b) { return a < b ? a : b; } | |
| /* A MATCH preceded by operations that build its mask (and sometimes its | |
| * pattern) in the bit store; returns the number of descriptors (<= 4). */ | |
| static int match_seq(struct desc *x, uint64_t words) | |
| { | |
| uint64_t bits = words * 64, pw = rng() % (words - 1), mw = (pw + 1) * 64; | |
| uint64_t lc = rng() % 100, l = lc < 45 ? rng() % 300 : lc < 95 ? rng() % (64 * umin(words, 64) + 1) : 0; | |
| uint64_t a = rng() % (bits - l + 1), m = rng() % 100; | |
| int n = 0; | |
| memset(x, 0, 4 * sizeof *x); | |
| if (m < 40) { /* contiguous k bits at offset o */ | |
| uint64_t k = 1 + rng() % 12, o = rng() % (64 - k + 1); | |
| x[n].op = OP_CLEARR; x[n].a = mw; x[n++].len = 64; | |
| x[n].op = OP_SETR; x[n].a = mw + o; x[n++].len = (uint32_t)k; | |
| } else if (m < 50) { /* zero mask */ | |
| x[n].op = OP_CLEARR; x[n].a = mw; x[n++].len = 64; | |
| } else if (m < 65 && l >= 64) { /* pattern = a haystack word, full mask */ | |
| uint64_t h = (a + 63) / 64 + rng() % (l / 64); | |
| if (h >= words) h = words - 1; | |
| x[n].op = OP_BULK; x[n].fn = FN_COPY; x[n].a = pw; x[n].src = (uint32_t)h; x[n++].len = 1; | |
| x[n].op = OP_SETR; x[n].a = mw; x[n++].len = 64; | |
| } else if (m < 80) { /* two far-apart bits */ | |
| x[n].op = OP_CLEARR; x[n].a = mw; x[n++].len = 64; | |
| x[n].op = OP_SETR; x[n].a = mw + rng() % 8; x[n++].len = 1; | |
| x[n].op = OP_SETR; x[n].a = mw + 56 + rng() % 8; x[n++].len = 1; | |
| } /* else the mask word as it is */ | |
| x[n].op = OP_MATCH; x[n].a = a; x[n].len = (uint32_t)l; | |
| x[n].src = rng() % 25 == 0 ? (uint32_t)(words - 1) : (uint32_t)pw; /* past the end: error */ | |
| x[n].fn = rng() % 12 == 0 ? (uint8_t)(2 + rng() % 6) : (uint8_t)(rng() % 2); | |
| return n + 1; | |
| } | |
| /* One or more descriptors (a MATCH sequence); returns how many (<= 4). */ | |
| static int random_desc(struct desc *x, uint64_t words, unsigned range_pct) | |
| { | |
| uint64_t bits = words * 64, r = rng(); | |
| memset(x, 0, sizeof *x); | |
| if (r % 100 >= range_pct) { /* single-bit */ | |
| x->op = (r >> 8) % 12 == 0 ? (uint8_t)(5 + (r >> 16) % 3) : (uint8_t)((r >> 16) % 5); | |
| if (r % 10 == 0) x->a = rng(); /* anywhere, mostly out of range */ | |
| else if (r % 10 == 1) x->a = bits + rng() % 4096; /* just past the end */ | |
| else x->a = rng() % bits; | |
| return 1; | |
| } | |
| uint64_t c = rng() % 100, l; | |
| if (c < 20) | |
| return match_seq(x, words); | |
| if (c < 23) { /* undefined */ | |
| x->op = (uint8_t)(5 + rng() % 3); x->a = rng() % bits; x->len = (uint32_t)(rng() % 64); | |
| } else if (c < 60) { /* bit range */ | |
| x->op = (uint8_t)(OP_COUNT + rng() % 6); | |
| uint64_t lc = rng() % 100; | |
| l = lc < 50 ? rng() % 130 : lc < 92 ? rng() % (64 * umin(words, 64) + 1) : lc < 96 ? 0 : bits; | |
| x->len = (uint32_t)l; | |
| x->a = rng() % 100 < 94 ? rng() % (bits - l + 1) : bits - l + 1 + rng() % 64; | |
| } else { /* BULK */ | |
| x->op = OP_BULK; | |
| x->fn = rng() % 20 == 0 ? (uint8_t)(5 + rng() % 3) : (uint8_t)(rng() % 5); | |
| x->dry = rng() % 5 == 0; | |
| l = rng() % (umin(words / 2, 256) + 1); | |
| x->len = (uint32_t)l; | |
| x->src = (uint32_t)(rng() % (words - l + 1)); | |
| uint64_t pc = rng() % 100, d = rng() % (words - l + 1); | |
| if (pc < 70) { | |
| for (int t = 0; t < 50 && l && d < x->src + l && x->src < d + l; t++) | |
| d = rng() % (words - l + 1); | |
| } else if (pc < 85) { | |
| d = x->src; | |
| } else if (pc < 95) { | |
| d = x->src + 1 + rng() % (l > 1 ? l - 1 : 1); /* overlapping: error */ | |
| } else { | |
| d = words - l + 1 + rng() % 8; /* past the end: error */ | |
| } | |
| x->a = d; | |
| } | |
| return 1; | |
| } | |
| /* ---------------------------------------------------------------- streaming */ | |
| /* | |
| * Streams total descriptors (a multiple of RUN_QUANTUM) and stores the 64-bit | |
| * results. Returns the elapsed milliseconds, or -1 on timeout. | |
| */ | |
| static int64_t stream(struct litepcie_dma_ctrl *dma, const uint8_t *desc, uint64_t total, uint64_t *res) | |
| { | |
| uint64_t sent = 0, got = 0; | |
| int64_t t0 = get_time_ms(), ms; | |
| dma->reader_enable = 1; | |
| dma->writer_enable = 1; | |
| while (got < total) { | |
| litepcie_dma_process(dma); | |
| char *buf; | |
| while (sent < total && (buf = litepcie_dma_next_write_buffer(dma))) { | |
| memcpy(buf, desc + sent * DESC_BYTES, DMA_BUFFER_SIZE); | |
| sent += DESCS_PER_BUFFER; | |
| } | |
| while ((buf = litepcie_dma_next_read_buffer(dma))) | |
| for (uint64_t i = 0; i < RESULTS_PER_BUFFER && got < total; i++, got++) | |
| res[got] = decode_result(buf + i * RESULT_BYTES); | |
| if (get_time_ms() - t0 > 120000) { | |
| fprintf(stderr, "bitacc: timeout after %" PRIu64 " of %" PRIu64 " results\n", got, total); | |
| break; | |
| } | |
| } | |
| ms = get_time_ms() - t0; | |
| dma->reader_enable = 0; | |
| dma->writer_enable = 0; | |
| litepcie_dma_process(dma); | |
| return got == total ? ms : -1; | |
| } | |
| static int open_engine(struct litepcie_dma_ctrl *dma, uint64_t *words) | |
| { | |
| uint32_t lanes, wpl; | |
| if (litepcie_dma_init(dma, device, 0)) | |
| return -1; | |
| int fd = dma->fds.fd; | |
| if (check_version(fd)) | |
| return -1; | |
| geometry(fd, &lanes, &wpl); | |
| *words = (uint64_t)lanes * wpl; | |
| printf("bit store: %u lanes x %u words = %" PRIu64 " KiB\n", lanes, wpl, *words * 8 / 1024); | |
| if (!(status(fd) & ST_IDLE)) { | |
| fprintf(stderr, "bitacc: engine not idle; reload the FPGA\n"); | |
| return -1; | |
| } | |
| return fd; | |
| } | |
| /* ---------------------------------------------------------------- selftest */ | |
| static int selftest(uint64_t ops, uint64_t seed, unsigned range_pct) | |
| { | |
| int rc = 1; | |
| uint64_t words; | |
| struct litepcie_dma_ctrl dma = {.use_reader = 1, .use_writer = 1}; | |
| uint64_t *mem = NULL, *expect = NULL, *res = NULL; | |
| uint8_t *desc = NULL; | |
| int fd = open_engine(&dma, &words); | |
| if (fd < 0) | |
| goto out; | |
| uint64_t total = (ops + RUN_QUANTUM - 1) / RUN_QUANTUM * RUN_QUANTUM; | |
| mem = calloc(words, sizeof *mem); | |
| expect = malloc(total * sizeof *expect); | |
| res = malloc(total * sizeof *res); | |
| desc = malloc(total * DESC_BYTES); | |
| if (!mem || !expect || !res || !desc) { perror("malloc"); goto out; } | |
| /* Load the bit store and a shadow copy. */ | |
| rng_state = seed ? seed : 1; | |
| for (uint64_t w = 0; w < words; w++) { | |
| mem[w] = rng(); | |
| if (word_write(fd, (uint32_t)w, mem[w])) goto out; | |
| } | |
| /* Build descriptors and expected results (model runs in descriptor order). */ | |
| uint64_t n_range = 0, n_range_ok = 0, n_match = 0, n_match_hit = 0; | |
| uint32_t ops_before = rd(fd, CSR_BITACC_OPS_ACCEPTED_ADDR); | |
| struct desc xs[4]; | |
| int nx = 0, ix = 0; | |
| for (uint64_t n = 0; n < total; n++) { | |
| struct desc x; | |
| if (ix == nx && n < ops) { | |
| nx = random_desc(xs, words, range_pct); | |
| ix = 0; | |
| } | |
| if (ix < nx) { | |
| x = xs[ix++]; /* may run past ops: padding then starts later */ | |
| } else { | |
| memset(&x, 0, sizeof x); | |
| x.op = OP_NOP; | |
| } | |
| expect[n] = model(mem, words, &x); | |
| if (x.op >= OP_COUNT) { | |
| n_range++; | |
| n_range_ok += expect[n] != RESULT_ERROR; | |
| n_match += x.op == OP_MATCH && expect[n] != RESULT_ERROR; | |
| n_match_hit += x.op == OP_MATCH && (expect[n] & 0xFF) == 0xA1; | |
| } | |
| encode(desc + n * DESC_BYTES, &x); | |
| } | |
| uint64_t mismatches = 0; | |
| int64_t ms = stream(&dma, desc, total, res); | |
| if (ms < 0) mismatches++; | |
| for (uint64_t n = 0; n < total && ms >= 0; n++) | |
| if (res[n] != expect[n] && mismatches++ < 10) | |
| fprintf(stderr, "result %" PRIu64 ": got 0x%016" PRIx64 ", expected 0x%016" PRIx64 "\n", | |
| n, res[n], expect[n]); | |
| uint32_t accepted = rd(fd, CSR_BITACC_OPS_ACCEPTED_ADDR) - ops_before; | |
| if (accepted != (uint32_t)total) { | |
| fprintf(stderr, "ops_accepted advanced by %u, expected %" PRIu64 "\n", accepted, total); | |
| mismatches++; | |
| } | |
| /* Compare the bit store. */ | |
| if (wait_status(fd, ST_IDLE, ST_IDLE)) mismatches++; | |
| for (uint64_t w = 0; w < words; w++) { | |
| uint64_t v; | |
| if (word_read(fd, (uint32_t)w, &v)) { mismatches++; break; } | |
| if (v != mem[w] && mismatches++ < 20) | |
| fprintf(stderr, "word %" PRIu64 ": 0x%016" PRIx64 ", expected 0x%016" PRIx64 "\n", w, v, mem[w]); | |
| } | |
| printf("selftest: %" PRIu64 " descriptors (%" PRIu64 " range, %" PRIu64 " of them valid, %" PRIu64 | |
| " valid MATCH with %" PRIu64 " hits), %" PRIu64 " words checked, %" PRIu64 " mismatches\n", | |
| total, n_range, n_range_ok, n_match, n_match_hit, words, mismatches); | |
| if (ms > 0) | |
| printf("DMA phase: %.2f M descriptors/s (%" PRId64 " ms)\n", (double)total / ms / 1000.0, ms); | |
| rc = mismatches ? 1 : 0; | |
| out: | |
| free(mem); free(expect); free(res); free(desc); | |
| litepcie_dma_cleanup(&dma); | |
| return rc; | |
| } | |
| /* ---------------------------------------------------------------- bench */ | |
| static int bench(const char *kind, uint64_t n) | |
| { | |
| int rc = 1; | |
| uint64_t words; | |
| struct litepcie_dma_ctrl dma = {.use_reader = 1, .use_writer = 1}; | |
| uint64_t *res = NULL; | |
| uint8_t *desc = NULL; | |
| int fd = open_engine(&dma, &words); | |
| if (fd < 0) | |
| goto out; | |
| uint64_t total = (n + RUN_QUANTUM - 1) / RUN_QUANTUM * RUN_QUANTUM; | |
| res = malloc(total * sizeof *res); | |
| desc = malloc(total * DESC_BYTES); | |
| if (!res || !desc) { perror("malloc"); goto out; } | |
| struct desc x = {0}; | |
| uint64_t bits_per_desc; | |
| if (!strcmp(kind, "get")) { | |
| x.op = OP_GET; bits_per_desc = 1; | |
| } else if (!strcmp(kind, "count")) { | |
| x.op = OP_COUNT; x.len = (uint32_t)(words * 64); bits_per_desc = words * 64; | |
| } else if (!strcmp(kind, "find")) { | |
| x.op = OP_FIND1; x.len = (uint32_t)(words * 64); bits_per_desc = words * 64; | |
| for (uint64_t w = 0; w < words; w++) | |
| if (word_write(fd, (uint32_t)w, 0)) goto out; | |
| } else if (!strcmp(kind, "match")) { | |
| if (!has_match) { fprintf(stderr, "bench: the gateware has no MATCH unit\n"); goto out; } | |
| x.op = OP_MATCH; x.fn = 0; x.src = 0; x.len = (uint32_t)(words * 64); bits_per_desc = words * 64; | |
| if (word_write(fd, 0, 0xBEEF) || word_write(fd, 1, 0xFFFF)) goto out; /* 16-bit pattern */ | |
| } else if (!strcmp(kind, "xor")) { | |
| x.op = OP_BULK; x.fn = FN_XOR; x.dry = 1; x.a = words / 2; x.src = 0; x.len = (uint32_t)(words / 2); | |
| bits_per_desc = words / 2 * 64; | |
| } else { | |
| fprintf(stderr, "bench: unknown kind '%s' (get, count, find, xor, match)\n", kind); | |
| goto out; | |
| } | |
| rng_state = 1; | |
| for (uint64_t i = 0; i < total; i++) { | |
| if (x.op == OP_GET) x.a = rng() % (words * 64); | |
| encode(desc + i * DESC_BYTES, &x); | |
| } | |
| int64_t ms = stream(&dma, desc, total, res); | |
| uint64_t errors = 0; | |
| for (uint64_t i = 0; i < total && ms >= 0; i++) | |
| errors += (res[i] & 0xFE) != 0xA0; | |
| if (ms < 0 || errors) { | |
| fprintf(stderr, "bench: %" PRIu64 " error results%s\n", errors, ms < 0 ? ", timeout" : ""); | |
| goto out; | |
| } | |
| printf("bench %s: %" PRIu64 " descriptors in %" PRId64 " ms", kind, total, ms); | |
| if (ms > 0) | |
| printf(": %.3f M descriptors/s, %.3f Gbit/s of bit store covered", | |
| (double)total / ms / 1000.0, (double)total * (double)bits_per_desc / ms / 1e6); | |
| printf("\n"); | |
| rc = 0; | |
| out: | |
| free(res); free(desc); | |
| litepcie_dma_cleanup(&dma); | |
| return rc; | |
| } | |
| static int info(void) | |
| { | |
| int fd = open(device, O_RDWR | O_CLOEXEC); | |
| if (fd < 0) { perror(device); return 1; } | |
| uint32_t lanes, wpl; | |
| geometry(fd, &lanes, &wpl); | |
| uint32_t s = status(fd); | |
| uint32_t f = rd(fd, CSR_BITACC_FEATURES_ADDR); | |
| printf("format %u, MATCH units %u, lanes %u, words per lane %u, idle %u, ops accepted %u, results sent %u\n", | |
| rd(fd, CSR_BITACC_VERSION_ADDR), | |
| (f >> CSR_BITACC_FEATURES_MATCH_OFFSET) & 1 ? (f >> CSR_BITACC_FEATURES_MATCH_LANES_OFFSET) & 0xff : 0, | |
| lanes, wpl, !!(s & ST_IDLE), | |
| rd(fd, CSR_BITACC_OPS_ACCEPTED_ADDR), rd(fd, CSR_BITACC_RESULTS_SENT_ADDR)); | |
| close(fd); | |
| return 0; | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| int i = 1; | |
| if (i + 1 < argc && !strcmp(argv[i], "-c")) { device = argv[i + 1]; i += 2; } | |
| if (i < argc && !strcmp(argv[i], "info")) | |
| return info(); | |
| if (i < argc && !strcmp(argv[i], "selftest")) { | |
| uint64_t ops = (i + 1 < argc) ? strtoull(argv[i + 1], NULL, 0) : 1000000; | |
| uint64_t seed = (i + 2 < argc) ? strtoull(argv[i + 2], NULL, 0) : 1; | |
| unsigned pct = (i + 3 < argc) ? (unsigned)strtoul(argv[i + 3], NULL, 0) : 5; | |
| return selftest(ops, seed, pct > 100 ? 100 : pct); | |
| } | |
| if (i + 1 < argc && !strcmp(argv[i], "bench")) { | |
| uint64_t n = (i + 2 < argc) ? strtoull(argv[i + 2], NULL, 0) : 100000; | |
| return bench(argv[i + 1], n); | |
| } | |
| fprintf(stderr, "usage: %s [-c device] info | selftest [operations] [seed] [range-percent]" | |
| " | bench get|count|find|xor|match [descriptors]\n", argv[0]); | |
| return 2; | |
| } | |