From 3db2bb980bae79d338a8218c07d6a931d3fa2097 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 3 Feb 2026 12:19:10 -0600 Subject: [PATCH] runners: emubd/kiwibd: Adopted lower-level bus+buffer bd sim After letting it sit for a bit, the previous byte+op sim comes across as overly clever in a way that is counter-productive. This is highlighted by erase-timing scaling in a confusing way when per-op. Fortunately, with a bit of tweaking, we can instead model the bd sim as separate bus+buffer timings. This seems more intuitive and is closer to how the actual hardware works. --- In the bus+buffer model, bd operations are simulated using two sets of timing estimates: buffer timings (nor) bus timings (nor) read_timing (0) readed_timing (40 ns/B) prog_timing (1563 ns/B) progged_timing (19 ns/B) erase_timing (10986 ns/B) erased_timing (0) Bus timings are a simple multiplier of the bytes read/progged/erased, while buffer timings are rounded up + aligned to the nearest "width": bd geometry (nor) bd buffers (nor) read_size (1 B) read_width (1 B) prog_size (1 B) prog_width (256 B) erase_size (4096 B) erase_width (4096 B) For most purposes, the width should just be the device's read/prog/erase buffer, but I went with the name width to try to keep it generic and avoid confusion with "buffer" elsewhere in the codebase. Some notes: - Like the byte+op sim, the bus+buffer sim allows penalizing small operations without artificially limiting what operations are possible. - Because buffer timings depend on read/prog/erase alignment, there's no simple equation from ops+bytes to bus+buffer. But as a tradeoff, this new sim more accurately penalizes unaligned operations. - All timings are still kept as per-byte instead of per-width. This has proven to be more flexible when benchmarking, as you usually what timings to scale with the relevant operation. - Currently this implemented by changing reads/progs/erases to track the number of "widths" read/progged/erased after alignment. Which makes the simtime formula roughly: simtime = reads*read_width*read_timing + readed*readed_timing (per-butter) (per-bus) I considered keeping separate counters for calls (read_calls/ prog_calls/erase_calls?), but not sure there's a good reason to. The theory behind these widths is there no functional difference between one big call vs multiple width sized calls, though maybe they would be useful for debugging? We can always add these later if they turn out to be useful. - When widths are disable (0), reads/progs/erases reverts to the number of read/prog/erase calls. This is the behavior when BENCH_SIMPLE is defined at compile-time. --- bd/lfs3_emubd.c | 24 +++++++---- bd/lfs3_emubd.h | 48 ++++++++++++++------- bd/lfs3_kiwibd.c | 24 +++++++---- bd/lfs3_kiwibd.h | 48 ++++++++++++++------- runners/bench_defines.h | 92 +++++++++++++++++++++++------------------ 5 files changed, 148 insertions(+), 88 deletions(-) diff --git a/bd/lfs3_emubd.c b/bd/lfs3_emubd.c index b7a0b148..0f64fb59 100644 --- a/bd/lfs3_emubd.c +++ b/bd/lfs3_emubd.c @@ -437,7 +437,9 @@ int lfs3_emubd_read(const struct lfs3_cfg *cfg, lfs3_block_t block, } // track reads - bd->reads += 1; + bd->reads += (lfs3_alignup(off + size, lfs3_max(bd->cfg->read_width, 1)) + - lfs3_aligndown(off, lfs3_max(bd->cfg->read_width, 1))) + / lfs3_max(bd->cfg->read_width, 1); bd->readed += size; if (bd->cfg->read_sleep) { int err = nanosleep(&(struct timespec){ @@ -749,7 +751,9 @@ progged:; } // track progs - bd->progs += 1; + bd->progs += (lfs3_alignup(off + size, lfs3_max(bd->cfg->prog_width, 1)) + - lfs3_aligndown(off, lfs3_max(bd->cfg->prog_width, 1))) + / lfs3_max(bd->cfg->prog_width, 1); bd->progged += size; if (bd->cfg->prog_sleep) { int err = nanosleep(&(struct timespec){ @@ -1045,7 +1049,9 @@ int lfs3_emubd_erase(const struct lfs3_cfg *cfg, lfs3_block_t block) { erased:; // track erases - bd->erases += 1; + bd->erases += lfs3_alignup(cfg->block_size, + lfs3_max(bd->cfg->erase_width, 1)) + / lfs3_max(bd->cfg->erase_width, 1); bd->erased += cfg->block_size; if (bd->cfg->erase_sleep) { int err = nanosleep(&(struct timespec){ @@ -1087,9 +1093,9 @@ lfs3_emubd_sns_t lfs3_emubd_simtime(const struct lfs3_cfg *cfg) { lfs3_emubd_t *bd = cfg->context; // error if all possible timings are zero - if (bd->cfg->reads_timing == 0 - && bd->cfg->progs_timing == 0 - && bd->cfg->erases_timing == 0 + if (bd->cfg->read_timing == 0 + && bd->cfg->prog_timing == 0 + && bd->cfg->erase_timing == 0 && bd->cfg->readed_timing == 0 && bd->cfg->progged_timing == 0 && bd->cfg->erased_timing == 0) { @@ -1098,9 +1104,9 @@ lfs3_emubd_sns_t lfs3_emubd_simtime(const struct lfs3_cfg *cfg) { } lfs3_emubd_ns_t ns - = (bd->cfg->reads_timing * bd->reads) - + (bd->cfg->progs_timing * bd->progs) - + (bd->cfg->erases_timing * bd->erases) + = (bd->cfg->read_timing * bd->reads*bd->cfg->read_width) + + (bd->cfg->prog_timing * bd->progs*bd->cfg->prog_width) + + (bd->cfg->erase_timing * bd->erases*bd->cfg->erase_width) + (bd->cfg->readed_timing * bd->readed) + (bd->cfg->progged_timing * bd->progged) + (bd->cfg->erased_timing * bd->erased); diff --git a/bd/lfs3_emubd.h b/bd/lfs3_emubd.h index 56dd027a..0b3ab9fc 100644 --- a/bd/lfs3_emubd.h +++ b/bd/lfs3_emubd.h @@ -74,28 +74,46 @@ struct lfs3_emubd_cfg { // does _not_ rely on this!). int32_t erase_value; - // Simulated read transaction timing in nanoseconds, this is added - // to simtime each read call, ignoring the requested size - lfs3_emubd_ns_t reads_timing; + // Simulated read width, this is only used for simulated read timing + // and emulates the physical read hardware on the device. Defaults + // to 1 byte. + lfs3_size_t read_width; - // Simulated prog transaction timing in nanoseconds, this is added - // to simtime each prog call, ignoring the requested size - lfs3_emubd_ns_t progs_timing; + // Simulated prog width, this is only used for simulated prog timing + // and emulates the physical prog hardware on the device. Defaults + // to 1 byte. + lfs3_size_t prog_width; - // Simulated erase transaction timing in nanoseconds, this is added - // to simtime each erase call, ignoring the requested size - lfs3_emubd_ns_t erases_timing; + // Simulated erase width, this is only used for simulated erase timing + // and emulates physical erase hardware on the device. Defaults to 1 + // byte. + lfs3_size_t erase_width; - // Simulated read byte timing in nanoseconds, this is scaled by the - // requested size and added to simtime each read call. + // Simulated per-byte read timing in nanoseconds, this is added to + // simtime each read call after aligning up to the necessary number + // of read_widths to emulate the read operation. + lfs3_emubd_ns_t read_timing; + + // Simulated per-byte prog timing in nanoseconds, this is added to + // simtime each prog call after aligning up to the necessary number + // of prog_widths to emulate the prog operation. + lfs3_emubd_ns_t prog_timing; + + // Simulated per-byte erase timing in nanoseconds, this is added to + // simtime each erase call after aligning up to the necessary number + // of erase_widths to emulate the erase operation. + lfs3_emubd_ns_t erase_timing; + + // Simulated per-byte read timing in nanoseconds, this ignores + // read_width and can be used to simulate relevant bus overhead. lfs3_emubd_ns_t readed_timing; - // Simulated prog byte timing in nanoseconds, this is scaled by the - // requested size and added to simtime each prog call. + // Simulated per-byte prog timing in nanoseconds, this ignores + // prog_width and can be used to simulate relevant bus overhead. lfs3_emubd_ns_t progged_timing; - // Simulated erase byte timing in nanoseconds, this is scaled by the - // requested size and added to simtime each erase call. + // Simulated per-byte erase timing in nanoseconds, this ignores + // erase_width and can be used to simulate relevant bus overhead. lfs3_emubd_ns_t erased_timing; // Artificial read transaction delay in nanoseconds, there is no diff --git a/bd/lfs3_kiwibd.c b/bd/lfs3_kiwibd.c index bf2cf08a..af817992 100644 --- a/bd/lfs3_kiwibd.c +++ b/bd/lfs3_kiwibd.c @@ -279,7 +279,9 @@ int lfs3_kiwibd_read(const struct lfs3_cfg *cfg, lfs3_block_t block, } // track reads - bd->reads += 1; + bd->reads += (lfs3_alignup(off + size, lfs3_max(bd->cfg->read_width, 1)) + - lfs3_aligndown(off, lfs3_max(bd->cfg->read_width, 1))) + / lfs3_max(bd->cfg->read_width, 1); bd->readed += size; if (bd->cfg->read_sleep) { int err = nanosleep(&(struct timespec){ @@ -408,7 +410,9 @@ int lfs3_kiwibd_prog(const struct lfs3_cfg *cfg, lfs3_block_t block, } // track progs - bd->progs += 1; + bd->progs += (lfs3_alignup(off + size, lfs3_max(bd->cfg->prog_width, 1)) + - lfs3_aligndown(off, lfs3_max(bd->cfg->prog_width, 1))) + / lfs3_max(bd->cfg->prog_width, 1); bd->progged += size; if (bd->cfg->prog_sleep) { int err = nanosleep(&(struct timespec){ @@ -470,7 +474,9 @@ int lfs3_kiwibd_erase(const struct lfs3_cfg *cfg, lfs3_block_t block) { erased:; // track erases - bd->erases += 1; + bd->erases += lfs3_alignup(cfg->block_size, + lfs3_max(bd->cfg->erase_width, 1)) + / lfs3_max(bd->cfg->erase_width, 1); bd->erased += cfg->block_size; if (bd->cfg->erase_sleep) { int err = nanosleep(&(struct timespec){ @@ -512,9 +518,9 @@ lfs3_kiwibd_sns_t lfs3_kiwibd_simtime(const struct lfs3_cfg *cfg) { lfs3_kiwibd_t *bd = cfg->context; // error if all possible timings are zero - if (bd->cfg->reads_timing == 0 - && bd->cfg->progs_timing == 0 - && bd->cfg->erases_timing == 0 + if (bd->cfg->read_timing == 0 + && bd->cfg->prog_timing == 0 + && bd->cfg->erase_timing == 0 && bd->cfg->readed_timing == 0 && bd->cfg->progged_timing == 0 && bd->cfg->erased_timing == 0) { @@ -523,9 +529,9 @@ lfs3_kiwibd_sns_t lfs3_kiwibd_simtime(const struct lfs3_cfg *cfg) { } lfs3_kiwibd_ns_t ns - = (bd->cfg->reads_timing * bd->reads) - + (bd->cfg->progs_timing * bd->progs) - + (bd->cfg->erases_timing * bd->erases) + = (bd->cfg->read_timing * bd->reads*bd->cfg->read_width) + + (bd->cfg->prog_timing * bd->progs*bd->cfg->prog_width) + + (bd->cfg->erase_timing * bd->erases*bd->cfg->erase_width) + (bd->cfg->readed_timing * bd->readed) + (bd->cfg->progged_timing * bd->progged) + (bd->cfg->erased_timing * bd->erased); diff --git a/bd/lfs3_kiwibd.h b/bd/lfs3_kiwibd.h index f459471d..18d89131 100644 --- a/bd/lfs3_kiwibd.h +++ b/bd/lfs3_kiwibd.h @@ -43,28 +43,46 @@ struct lfs3_kiwibd_cfg { // does _not_ rely on this!). int32_t erase_value; - // Simulated read transaction timing in nanoseconds, this is added - // to simtime each read call, ignoring the requested size - lfs3_kiwibd_ns_t reads_timing; + // Simulated read width, this is only used for simulated read timing + // and emulates the physical read hardware on the device. Defaults + // to 1 byte. + lfs3_size_t read_width; - // Simulated prog transaction timing in nanoseconds, this is added - // to simtime each prog call, ignoring the requested size - lfs3_kiwibd_ns_t progs_timing; + // Simulated prog width, this is only used for simulated prog timing + // and emulates the physical prog hardware on the device. Defaults + // to 1 byte. + lfs3_size_t prog_width; - // Simulated erase transaction timing in nanoseconds, this is added - // to simtime each erase call, ignoring the requested size - lfs3_kiwibd_ns_t erases_timing; + // Simulated erase width, this is only used for simulated erase timing + // and emulates physical erase hardware on the device. Defaults to 1 + // byte. + lfs3_size_t erase_width; - // Simulated read byte timing in nanoseconds, this is scaled by the - // requested size and added to simtime each read call. + // Simulated per-byte read timing in nanoseconds, this is added to + // simtime each read call after aligning up to the necessary number + // of read_widths to emulate the read operation. + lfs3_kiwibd_ns_t read_timing; + + // Simulated per-byte prog timing in nanoseconds, this is added to + // simtime each prog call after aligning up to the necessary number + // of prog_widths to emulate the prog operation. + lfs3_kiwibd_ns_t prog_timing; + + // Simulated per-byte erase timing in nanoseconds, this is added to + // simtime each erase call after aligning up to the necessary number + // of erase_widths to emulate the erase operation. + lfs3_kiwibd_ns_t erase_timing; + + // Simulated per-byte read timing in nanoseconds, this ignores + // read_width and can be used to simulate relevant bus overhead. lfs3_kiwibd_ns_t readed_timing; - // Simulated prog byte timing in nanoseconds, this is scaled by the - // requested size and added to simtime each prog call. + // Simulated per-byte prog timing in nanoseconds, this ignores + // prog_width and can be used to simulate relevant bus overhead. lfs3_kiwibd_ns_t progged_timing; - // Simulated erase byte timing in nanoseconds, this is scaled by the - // requested size and added to simtime each erase call. + // Simulated per-byte erase timing in nanoseconds, this ignores + // erase_width and can be used to simulate relevant bus overhead. lfs3_kiwibd_ns_t erased_timing; // Artificial read transaction delay in nanoseconds, there is no diff --git a/runners/bench_defines.h b/runners/bench_defines.h index 5456d12f..1d90417e 100644 --- a/runners/bench_defines.h +++ b/runners/bench_defines.h @@ -51,30 +51,33 @@ // erased=10986ns/B tSE=45 ms, sector=4096 (45 ms / 4096) // // less-simple: - // reads=0ns (no transaction cost) - // progs=400000ns tPP=0.4 ms, page=256 - // erases=0ns (no transaction cost) + // read=0ns/B (no transaction cost) + // prog=1563ns/B tPP=0.4 ms, page=256 (0.4 ms / 256) + // erase=10986ns/B tSE=45 ms, sector=4096 (45 ms / 4096) // readed=40ns/B fR=50 MHz, quad read (20 ns * 8/4) - // progged=1484ns/B tPP=0.4 ms (((4096/256)*0.4ms - 0.4ms)/4096 + bus) - // erased=10986ns/B tSE=45 ms, sector=4096 (45 ms / 4096) - // - // note we always treat erases as per-byte to simplify benchmarking - // across different block sizes + // progged=19ns/B (bus) + // erased=0ns/B (no bus cost) // #ifdef BENCH_SIMPLE - BENCH_DEFINE(READS_TIMING, 0 ) - BENCH_DEFINE(PROGS_TIMING, 0 ) - BENCH_DEFINE(ERASES_TIMING, 0 ) + BENCH_DEFINE(READ_WIDTH, 0 ) + BENCH_DEFINE(PROG_WIDTH, 0 ) + BENCH_DEFINE(ERASE_WIDTH, 0 ) + BENCH_DEFINE(READ_TIMING, 0 ) + BENCH_DEFINE(PROG_TIMING, 0 ) + BENCH_DEFINE(ERASE_TIMING, 0 ) BENCH_DEFINE(READED_TIMING, 40 ) BENCH_DEFINE(PROGGED_TIMING, 1582 ) BENCH_DEFINE(ERASED_TIMING, 10986 ) #else - BENCH_DEFINE(READS_TIMING, 0 ) - BENCH_DEFINE(PROGS_TIMING, 400000 ) - BENCH_DEFINE(ERASES_TIMING, 0 ) + BENCH_DEFINE(READ_WIDTH, 0 ) + BENCH_DEFINE(PROG_WIDTH, 256 ) + BENCH_DEFINE(ERASE_WIDTH, BLOCK_SIZE ) + BENCH_DEFINE(READ_TIMING, 0 ) + BENCH_DEFINE(PROG_TIMING, 1563 ) + BENCH_DEFINE(ERASE_TIMING, 10986 ) BENCH_DEFINE(READED_TIMING, 40 ) - BENCH_DEFINE(PROGGED_TIMING, 1484 ) - BENCH_DEFINE(ERASED_TIMING, 10986 ) + BENCH_DEFINE(PROGGED_TIMING, 19 ) + BENCH_DEFINE(ERASED_TIMING, 0 ) #endif #else // NAND flash timings @@ -91,30 +94,33 @@ // erased=15ns/B tBE=2 ms, block=131072 (2 ms / 131072) // // less-simple: - // reads=25000ns tRD1=25 us, p=2048, s=512 - // progs=250000ns tPP=250 us, p=2048, s=512 - // erases=0ns (no transaction cost) - // readed=31ns/B tRD1=25 us (((131072/2048)*25us - 25us)/131072 + bus) - // progged=139ns/B tPP=250 us (((131072/2048)*250us - 250us)/131072 + bus) - // erased=15ns/B tBE=2 ms, block=131072 (2 ms / 131072) - // - // note we always treat erases as per-byte to simplify benchmarking - // across different block sizes + // read=12ns/B tRD1=25 us, p=2048, s=512 (25 us / 2048) + // prog=122ns/B tPP=250 us, p=2048, s=512 (250 us / 2048) + // erase=15ns/B tBE=2 ms, block=131072 (2 ms / 131072) + // readed=19ns/B (bus) + // progged=19ns/B (bus) + // erased=0ns/B (no bus cost) // #ifdef BENCH_SIMPLE - BENCH_DEFINE(READS_TIMING, 0 ) - BENCH_DEFINE(PROGS_TIMING, 0 ) - BENCH_DEFINE(ERASES_TIMING, 0 ) + BENCH_DEFINE(READ_WIDTH, 0 ) + BENCH_DEFINE(PROG_WIDTH, 0 ) + BENCH_DEFINE(ERASE_WIDTH, 0 ) + BENCH_DEFINE(READ_TIMING, 0 ) + BENCH_DEFINE(PROG_TIMING, 0 ) + BENCH_DEFINE(ERASE_TIMING, 0 ) BENCH_DEFINE(READED_TIMING, 31 ) BENCH_DEFINE(PROGGED_TIMING, 141 ) BENCH_DEFINE(ERASED_TIMING, 15 ) #else - BENCH_DEFINE(READS_TIMING, 25000 ) - BENCH_DEFINE(PROGS_TIMING, 250000 ) - BENCH_DEFINE(ERASES_TIMING, 0 ) - BENCH_DEFINE(READED_TIMING, 31 ) - BENCH_DEFINE(PROGGED_TIMING, 139 ) - BENCH_DEFINE(ERASED_TIMING, 15 ) + BENCH_DEFINE(READ_WIDTH, 2048 ) + BENCH_DEFINE(PROG_WIDTH, 2048 ) + BENCH_DEFINE(ERASE_WIDTH, BLOCK_SIZE ) + BENCH_DEFINE(READ_TIMING, 12 ) + BENCH_DEFINE(PROG_TIMING, 122 ) + BENCH_DEFINE(ERASE_TIMING, 15 ) + BENCH_DEFINE(READED_TIMING, 19 ) + BENCH_DEFINE(PROGGED_TIMING, 19 ) + BENCH_DEFINE(ERASED_TIMING, 0 ) #endif #endif #ifndef BENCH_KIWIBD @@ -170,9 +176,12 @@ BENCH_BDCFG_CFG #endif .erase_value = ERASE_VALUE, - .reads_timing = READS_TIMING, - .progs_timing = PROGS_TIMING, - .erases_timing = ERASES_TIMING, + .read_width = READ_WIDTH, + .prog_width = PROG_WIDTH, + .erase_width = ERASE_WIDTH, + .read_timing = READ_TIMING, + .prog_timing = PROG_TIMING, + .erase_timing = ERASE_TIMING, .readed_timing = READED_TIMING, .progged_timing = PROGGED_TIMING, .erased_timing = ERASED_TIMING, @@ -188,9 +197,12 @@ BENCH_BDCFG_CFG #endif .erase_value = ERASE_VALUE, - .reads_timing = READS_TIMING, - .progs_timing = PROGS_TIMING, - .erases_timing = ERASES_TIMING, + .read_width = READ_WIDTH, + .prog_width = PROG_WIDTH, + .erase_width = ERASE_WIDTH, + .read_timing = READ_TIMING, + .prog_timing = PROG_TIMING, + .erase_timing = ERASE_TIMING, .readed_timing = READED_TIMING, .progged_timing = PROGGED_TIMING, .erased_timing = ERASED_TIMING,