3db2bb980b
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.
214 lines
10 KiB
C
214 lines
10 KiB
C
// littlefs bench runner defines
|
|
|
|
|
|
// preconfigured defines that control how benches run
|
|
#ifdef BENCH_DEFINE
|
|
// name value (overridable)
|
|
#ifndef BENCH_NAND
|
|
// NOR flash geometry
|
|
BENCH_DEFINE(READ_SIZE, 1 )
|
|
BENCH_DEFINE(PROG_SIZE, 1 )
|
|
BENCH_DEFINE(BLOCK_SIZE, 4096 )
|
|
#else
|
|
// NAND flash geometry
|
|
BENCH_DEFINE(READ_SIZE, 1 )
|
|
BENCH_DEFINE(PROG_SIZE, 512 )
|
|
BENCH_DEFINE(BLOCK_SIZE, 131072 )
|
|
#endif
|
|
BENCH_DEFINE(BLOCK_COUNT, DISK_SIZE/BLOCK_SIZE )
|
|
BENCH_DEFINE(DISK_SIZE, 1024*1024 )
|
|
BENCH_DEFINE(BLOCK_RECYCLES, -1 )
|
|
BENCH_DEFINE(RCACHE_SIZE, LFS3_MAX(16, READ_SIZE) )
|
|
BENCH_DEFINE(PCACHE_SIZE, LFS3_MAX(16, PROG_SIZE) )
|
|
BENCH_DEFINE(FCACHE_SIZE, 16 )
|
|
BENCH_DEFINE(LOOKAHEAD_SIZE, 16 )
|
|
BENCH_DEFINE(GC_FLAGS, LFS3_GC_GC )
|
|
BENCH_DEFINE(GC_STEPS, 0 )
|
|
BENCH_DEFINE(GC_LOOKAHEAD_THRESH, -1 )
|
|
BENCH_DEFINE(GC_LOOKGBMAP_THRESH, -1 )
|
|
BENCH_DEFINE(GC_PREERASE_COUNT, -1 )
|
|
BENCH_DEFINE(GC_COMPACT_THRESH, 0 )
|
|
BENCH_DEFINE(SHRUB_SIZE, BLOCK_SIZE/4 )
|
|
BENCH_DEFINE(FRAGMENT_SIZE, LFS3_MIN(BLOCK_SIZE/8, 512) )
|
|
BENCH_DEFINE(CRYSTAL_THRESH, BLOCK_SIZE/8 )
|
|
BENCH_DEFINE(LOOKGBMAP_THRESH, BLOCK_COUNT/4 )
|
|
BENCH_DEFINE(ERASE_VALUE, 0xff )
|
|
#ifndef BENCH_NAND
|
|
// NOR flash timings
|
|
//
|
|
// based on w25q64jv:
|
|
// https://www.winbond.com/resource-files/
|
|
// W25Q64JV%20RevM%2012242024%20Plus.pdf
|
|
//
|
|
// note one thing unique to NOR flash is the extreme erase cost
|
|
//
|
|
// FR=104 MHz, quad prog (9.6 ns * 8/4)
|
|
// => +~19 ns for bus (not read!)
|
|
//
|
|
// simple:
|
|
// readed=40ns/B fR=50 MHz, quad read (20 ns * 8/4)
|
|
// progged=1582ns/B tPP=0.4 ms, page=256 (0.4 ms / 256 + bus)
|
|
// erased=10986ns/B tSE=45 ms, sector=4096 (45 ms / 4096)
|
|
//
|
|
// less-simple:
|
|
// 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=19ns/B (bus)
|
|
// erased=0ns/B (no bus cost)
|
|
//
|
|
#ifdef BENCH_SIMPLE
|
|
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(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, 19 )
|
|
BENCH_DEFINE(ERASED_TIMING, 0 )
|
|
#endif
|
|
#else
|
|
// NAND flash timings
|
|
//
|
|
// based on w25n01gv:
|
|
// https://www.winbond.com/resource-files/W25N01GV%20Rev%20R%20070323.pdf
|
|
//
|
|
// FR=104 MHz, quad read/prog (9.6 ns * 8/4)
|
|
// => +~19 ns for bus
|
|
//
|
|
// simple:
|
|
// readed=31ns/B tRD1=25 us, p=2048, s=512 (25 us / 2048 + bus)
|
|
// progged=141ns/B tPP=250 us, p=2048, s=512 (250 us / 2048 + bus)
|
|
// erased=15ns/B tBE=2 ms, block=131072 (2 ms / 131072)
|
|
//
|
|
// less-simple:
|
|
// 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(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(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
|
|
BENCH_DEFINE(ERASE_CYCLES, 0 )
|
|
BENCH_DEFINE(BADBLOCK_BEHAVIOR, LFS3_EMUBD_BADBLOCK_PROGERROR )
|
|
BENCH_DEFINE(POWERLOSS_BEHAVIOR, LFS3_EMUBD_POWERLOSS_ATOMIC )
|
|
BENCH_DEFINE(BD_SEED, 0 )
|
|
#endif
|
|
#endif
|
|
|
|
|
|
// struct lfs3_cfg definition
|
|
#ifdef BENCH_CFG
|
|
struct lfs3_cfg _cfg = {
|
|
#ifdef BENCH_CFG_CFG
|
|
BENCH_CFG_CFG
|
|
#endif
|
|
.read_size = READ_SIZE,
|
|
.prog_size = PROG_SIZE,
|
|
.block_size = BLOCK_SIZE,
|
|
.block_count = BLOCK_COUNT,
|
|
.block_recycles = BLOCK_RECYCLES,
|
|
.rcache_size = RCACHE_SIZE,
|
|
.pcache_size = PCACHE_SIZE,
|
|
.fcache_size = FCACHE_SIZE,
|
|
.lookahead_size = LOOKAHEAD_SIZE,
|
|
#ifdef LFS3_GBMAP
|
|
.gc_lookgbmap_thresh = GC_LOOKGBMAP_THRESH,
|
|
.lookgbmap_thresh = LOOKGBMAP_THRESH,
|
|
#endif
|
|
#ifdef LFS3_PREERASE
|
|
.gc_preerase_count = GC_PREERASE_COUNT,
|
|
#endif
|
|
#ifdef LFS3_GC
|
|
.gc_flags = GC_FLAGS,
|
|
.gc_steps = GC_STEPS,
|
|
#endif
|
|
.gc_lookahead_thresh = GC_LOOKAHEAD_THRESH,
|
|
.gc_compact_thresh = GC_COMPACT_THRESH,
|
|
.shrub_size = SHRUB_SIZE,
|
|
.fragment_size = FRAGMENT_SIZE,
|
|
.crystal_thresh = CRYSTAL_THRESH,
|
|
};
|
|
struct lfs3_cfg *BENCH_CFG = &_cfg;
|
|
#endif
|
|
|
|
|
|
// struct lfs3_*bd_cfg definition
|
|
#ifdef BENCH_BDCFG
|
|
#ifndef BENCH_KIWIBD
|
|
struct lfs3_emubd_cfg _bdcfg = {
|
|
#ifdef BENCH_BDCFG_CFG
|
|
BENCH_BDCFG_CFG
|
|
#endif
|
|
.erase_value = ERASE_VALUE,
|
|
.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,
|
|
.erase_cycles = ERASE_CYCLES,
|
|
.badblock_behavior = BADBLOCK_BEHAVIOR,
|
|
.powerloss_behavior = POWERLOSS_BEHAVIOR,
|
|
.seed = BD_SEED,
|
|
};
|
|
struct lfs3_emubd_cfg *BENCH_BDCFG = &_bdcfg;
|
|
#else
|
|
struct lfs3_kiwibd_cfg _bdcfg = {
|
|
#ifdef BENCH_BDCFG_CFG
|
|
BENCH_BDCFG_CFG
|
|
#endif
|
|
.erase_value = ERASE_VALUE,
|
|
.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,
|
|
};
|
|
struct lfs3_kiwibd_cfg *BENCH_BDCFG = &_bdcfg;
|
|
#endif
|
|
#endif
|
|
|