Files
DCU_DesktopController/scripts/test_pram_batch_sim.py
T

239 lines
8.1 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""test_pram_batch_sim.py - 主机侧仿真:验证 pramRead/pramWrite 批量化(1.6.0)
与旧实现(逐 DWORD 轮询)逐字节等价。
模型依据 LAN9252 DS00001909A §12.11.3/12.11.4:
- PRAM FIFO 16 deep x 32bit;AVAIL_CNT(CMD bits12:8)=可连续读/写的 DWORD 数
- 读引擎按 DWORD 对齐向下取源:第 i 个 FIFO DWORD = 源地址 aligned(addr)+4i 起 4 字节;
线性有效窗口 = [0, len),即 DWORD i 的字节 k 有效 iff 0 <= 4i+k-lead < len
- 写引擎按同一线性窗口落盘,主站推满一个 DWORD 计一次
- 引擎以有限速率搬运(rate),主站 respect AVAIL_CNT 就不会溢 FIFO/读空洞
对全部 (addr 对齐 x len 1..70/96/384/640 x 引擎节奏) 断言:
新旧算法产出逐字节一致,且主站从不超出 AVAIL_CNT(引擎内 assert)。
"""
import random
FIFO_DW = 16 # FIFO 深度 16 DWORD
WINDOW_DW = 8 # 别名窗口一程 8 DWORD (0x00-0x1C / 0x20-0x3C)
PRAM_BASE = 0x1000
PRAM_SIZE = 0x1000
def byte_valid(i, k, lead, ln):
"""DWORD i 的字节 k 是否落在线性有效窗口 [0, ln) 内。"""
return 0 <= 4 * i + k - lead < ln
class ReadEngine:
def __init__(self, mem, addr, ln, rate):
self.mem = mem
self.lead = addr & 3
self.ln = ln
self.rate = max(1, rate)
self.total_dw = (ln + self.lead + 3) // 4
self.start = (addr & ~3) - PRAM_BASE # mem 内偏移
self.pushed = 0
self.popped = 0
self.data = []
def pump(self):
n = 0
while (self.pushed - self.popped < FIFO_DW and self.pushed < self.total_dw
and n < self.rate):
i = self.pushed
dw = bytearray(4)
for k in range(4):
if byte_valid(i, k, self.lead, self.ln):
dw[k] = self.mem[self.start + 4 * i + k]
self.data.append(bytes(dw))
self.pushed += 1
n += 1
return n
def avail_cnt(self):
return self.pushed - self.popped
def pop(self):
assert self.popped < self.pushed, "主站弹出超出 AVAIL_CNT!"
dw = self.data[self.popped]
self.popped += 1
return dw
def pram_read_old(eng, addr, ln, poll_budget=2000):
out = bytearray()
done = 0
polls = 0
while done < ln:
while eng.avail_cnt() == 0:
polls += 1
if polls > poll_budget:
return bytes(out), False
eng.pump()
dw = eng.pop()
lead = (addr & 3) if done == 0 else 0
take = min(ln - done, 4 - lead)
out += dw[lead:lead + take]
done += take
eng.pump()
return bytes(out), True
def pram_read_new(eng, addr, ln, poll_budget=2000):
out = bytearray()
done = 0
polls = 0
while done < ln:
while eng.avail_cnt() == 0:
polls += 1
if polls > poll_budget:
return bytes(out), False
eng.pump()
batch = eng.avail_cnt()
if batch == 0 or batch > WINDOW_DW:
batch = 1
lead0 = (addr & 3) if done == 0 else 0
need_dw = (ln - done + lead0 + 3) // 4
batch = min(batch, need_dw)
for _ in range(batch):
dw = eng.pop()
lead = (addr & 3) if done == 0 else 0
take = min(ln - done, 4 - lead)
out += dw[lead:lead + take]
done += take
eng.pump()
return bytes(out), True
class WriteEngine:
def __init__(self, addr, ln, rate):
self.lead = addr & 3
self.ln = ln
self.rate = max(1, rate)
self.total_dw = (ln + self.lead + 3) // 4
self.start = (addr & ~3) - PRAM_BASE
self.fifo = []
self.consumed_dw = 0
self.mem = bytearray(PRAM_SIZE)
def pump(self):
n = 0
while self.fifo and n < self.rate:
dw = self.fifo.pop(0)
i = self.consumed_dw
for k in range(4):
if byte_valid(i, k, self.lead, self.ln):
self.mem[self.start + 4 * i + k] = dw[k]
self.consumed_dw += 1
n += 1
return n
def space_cnt(self):
return FIFO_DW - len(self.fifo)
def push(self, dw):
assert len(self.fifo) < FIFO_DW, "主站写溢出 FIFO!"
self.fifo.append(bytes(dw))
def _write_common(eng, addr, buf, poll_budget, batched):
ln = len(buf)
done = 0
polls = 0
while done < ln:
while eng.space_cnt() == 0:
polls += 1
if polls > poll_budget:
return False
eng.pump()
n_iter = 1
if batched:
batch = eng.space_cnt()
if batch == 0 or batch > WINDOW_DW:
batch = 1
lead0 = (addr & 3) if done == 0 else 0
need_dw = (ln - done + lead0 + 3) // 4
n_iter = min(batch, need_dw)
for _ in range(n_iter):
lead = (addr & 3) if done == 0 else 0
take = min(ln - done, 4 - lead)
dw = bytearray(4)
for i in range(take):
dw[lead + i] = buf[done + i]
eng.push(dw)
done += take
eng.pump()
budget = poll_budget
while eng.consumed_dw < eng.total_dw and budget:
eng.pump()
budget -= 1
return eng.consumed_dw == eng.total_dw
def pram_write_old(eng, addr, buf, poll_budget=2000):
return _write_common(eng, addr, buf, poll_budget, batched=False)
def pram_write_new(eng, addr, buf, poll_budget=2000):
return _write_common(eng, addr, buf, poll_budget, batched=True)
def run():
random.seed(20260905)
cases = 0
lens = list(range(1, 71)) + [96, 384, 640] # 覆盖 SM2(384B)/SM3(640B) 实际长度
for align in range(4):
for ln in lens:
for rate in (1, 3, 16, 64):
src = bytes(random.randrange(1, 256) for _ in range(ln))
base = random.randrange(PRAM_SIZE - ln - 8)
addr = PRAM_BASE + base + align
mem = bytearray(PRAM_SIZE)
mem[base + align: base + align + ln] = src
# 读等价:新旧都须完整取回 src
e1 = ReadEngine(mem, addr, ln, rate); e1.pump()
r_old, ok1 = pram_read_old(e1, addr, ln)
e2 = ReadEngine(mem, addr, ln, rate); e2.pump()
r_new, ok2 = pram_read_new(e2, addr, ln)
assert ok1 and ok2, f"read 未完成 align={align} len={ln} rate={rate}"
assert r_old == src, f"read 旧实现不符 align={align} len={ln} rate={rate}"
assert r_new == src, f"read 新实现不符 align={align} len={ln} rate={rate}"
# 写等价:新旧都须把 src 落到同一片 PRAM
w1 = WriteEngine(addr, ln, rate); w1.pump()
assert pram_write_old(w1, addr, src), f"write 未完成 {align}/{ln}/{rate}"
w2 = WriteEngine(addr, ln, rate); w2.pump()
assert pram_write_new(w2, addr, src), f"write 未完成 {align}/{ln}/{rate}"
assert bytes(w1.mem[base + align: base + align + ln]) == src, \
f"write 旧不符 {align}/{ln}/{rate}"
assert bytes(w2.mem[base + align: base + align + ln]) == src, \
f"write 新不符 {align}/{ln}/{rate}"
cases += 1
print(f"OK: {cases} 组 (对齐x长度x引擎节奏) 新旧算法逐字节等价")
# 统计新实现的轮询次数收益(代表性场景:SM3 640B DWORD 对齐,引擎速率 16)
for rate in (1, 3, 16):
src = bytes(random.randrange(1, 256) for _ in range(640))
e = ReadEngine(bytearray(PRAM_SIZE), PRAM_BASE, 640, rate); e.pump()
_, p1 = _count_polls(e, 640)
print(f" 640B@rate{rate}: 新算法主站外圈轮询 {p1} 次")
def _count_polls(eng, ln):
done = 0
polls = 0
while done < ln:
while eng.avail_cnt() == 0:
polls += 1
eng.pump()
batch = min(eng.avail_cnt(), WINDOW_DW)
lead0 = 0 if done == 0 else 0
batch = min(batch, (ln - done + lead0 + 3) // 4)
for _ in range(batch):
eng.pop()
done += 4
return None, polls
if __name__ == "__main__":
run()