Skip to content

Add Dynamic Memory Array Support - #25

Open
hmljy2020 wants to merge 3 commits into
PTO-ISA:mainfrom
hmljy2020:codex/dynamic-memory-array
Open

Add Dynamic Memory Array Support#25
hmljy2020 wants to merge 3 commits into
PTO-ISA:mainfrom
hmljy2020:codex/dynamic-memory-array

Conversation

@hmljy2020

Copy link
Copy Markdown
Contributor

变更概述

本 PR 在现有显式 Memory 能力之上,额外实现了动态 Memory Array。

Python 前端现在可以通过:

banks = ac.array((2, 2), ac.memory(...))
responses = banks[row, col].request(...)

声明多维 memory bank array,并使用运行时 Queue 数据动态选择目标 bank。

本次实现覆盖完整链路:

  • Python 前端 API 与 lowering
  • ACIR ac.arrayac.array.invokeac.array.invoke.yield
  • QueueGraph memory-array plan
  • gfsim 动态 bank array
  • PYC 与 Verilog lowering
  • verifier、负例测试和端到端示例

Python 前端示例

import agentic_circuit as ac


@ac.struct
class Request:
    address: ac.u8
    id: ac.u8
    write: ac.u1
    data: ac.u16


@ac.system
def memory_array() -> None:
    requests = ac.source(Request, depth=8)

    with ac.scope("sram"):
        banks = ac.array(
            (2, 2),
            ac.memory(
                ac.u16,
                entries=16,
                init=0,
                latency=3,
            ),
        )

        def decode(request):
            row = (request.address >> 5) & 1
            col = (request.address >> 4) & 1
            address = request.address & 15
            return (
                row,
                col,
                address,
                request.id,
                request.write,
                request.data,
            )

        (
            row,
            col,
            address,
            request_id,
            write,
            data,
        ) = requests.apply(decode)

        responses = banks[row, col].request(
            id=request_id,
            address=address,
            write=write,
            data=data,
            depth=4,
        )

    ac.sink(responses)

示例说明

1. 声明请求类型

@ac.struct
class Request:
    address: ac.u8
    id: ac.u8
    write: ac.u1
    data: ac.u16

每个请求包含:

  • address:同时编码 bank 坐标和 bank 内地址
  • id:用于匹配可能乱序完成的响应
  • write:区分读请求和写请求
  • data:写入数据;响应时承载读取值或 old-data

2. 声明二维 Memory Array

banks = ac.array(
    (2, 2),
    ac.memory(ac.u16, entries=16, init=0, latency=3),
)

该声明创建一个 2 × 2 的 memory bank array,共四个物理 bank。

每个 bank:

  • 保存独立的 storage
  • 具有独立的 busy 状态
  • 允许一个 outstanding 请求
  • 使用相同的数据类型、深度、初始值和访问 latency

因此,不同 bank 可以并行处理请求;同一个 bank 上的后续请求仍会受到 backpressure。

3. 从地址中解析动态 bank 坐标

row = (request.address >> 5) & 1
col = (request.address >> 4) & 1
address = request.address & 15

示例使用:

  • bit 5 选择 row
  • bit 4 选择 column
  • bit 0–3 作为 bank 内地址

rowcol 都来自运行时 Queue token,不是 elaboration-time 常量。

4. 使用 tuple apply 保持请求字段关联

(row, col, address, request_id, write, data) = requests.apply(decode)

tuple-valued apply 只消费一个输入 Queue token,并同时产生本次访问所需的所有值。

这样可以保证:

  • bank selector
  • bank 内地址
  • request ID
  • 读写标志
  • 写入数据

始终来自同一个请求,不会因为拆成多条独立 Queue 而失去关联。

5. 动态选择 bank 并发起请求

responses = banks[row, col].request(
    id=request_id,
    address=address,
    write=write,
    data=data,
    depth=4,
)

banks[row, col] 会 lower 为动态 array invoke,而不是静态展开后的固定选择。

运行时只访问被选中的 bank:

  • 未选中的 bank 不改变状态
  • 不同 bank 可以并行
  • 同一 bank 在 busy 时对新请求施加 backpressure
  • response Queue 阻塞时,对应 bank 保持 busy

6. 使用 ID 匹配响应

不同 bank 的访问可以同时进行,因此响应按照实际完成顺序返回,不保证全局请求顺序。

响应保留请求的 id,调用方应通过 ID 匹配请求和响应。

Memory Array 时序语义

本实现遵循以下规则:

  • 每个 bank 是一个独立的单端口 memory
  • 每个 bank 最多允许一个 outstanding 请求
  • 请求被接受后,该 bank 进入 busy
  • 访问经过声明的 memory latency 后产生响应
  • 只有 response Queue 接受响应后,该 bank 才释放 busy
  • 写请求返回写入前的 old-data
  • 不同 bank 可以并行访问和乱序完成
  • 越界 bank selector 或 bank 内地址会产生明确错误

当前 Memory 与 Memory Array invoke 只支持 rate=1。遇到 rate>1 会明确拒绝,避免被 multi-rate scheduler 静默错误执行。

后端实现

ACIR

新增并验证:

  • ac.array
  • ac.array.invoke
  • ac.array.invoke.yield

verifier 会检查:

  • array owner 和 invoke 的 scope
  • array rank 与 selector 数量
  • selector 类型
  • payload 与结果类型
  • endpoint ordinal
  • owner 名称和引用关系

ac.array owner 不是 Pure operation,canonicalization 不会删除物理 array 实例。

QueueGraph 与 gfsim

QueueGraph 保存 array shape、bank 配置、invoke endpoint 和 Queue 连接关系。

gfsim 为每个 bank 分别维护:

  • storage
  • busy
  • request context
  • old-data
  • response-ready epoch

只有动态选择的 bank 会处理请求,不同 bank 可以独立推进。

PYC 与 Verilog

PYC lowering 为每个物理 bank 生成一个 pyc.sync_mem

对于 2 × 2 array,会生成四个 memory primitive,并包含:

  • bank selector
  • owner registers
  • response mux
  • ready/valid backpressure
  • request ID 与 response payload 对齐逻辑

当前每个 service array 限制为一个 invoke endpoint。

示例运行结果

examples/memory/memory_array.py 的 harness 会向三个不同 bank 分别写入数据,再发起读取。

预期输出:

responses=6 bank00=41 bank01=52 bank10=63

该结果验证:

  • 六个请求均完成
  • bank [0, 0] 保存 41
  • bank [0, 1] 保存 52
  • bank [1, 0] 保存 63
  • response ID 能正确关联乱序完成的 bank 访问

其他变更

  • contract epoch 更新为 0.4
  • 保留上游 JIT、multi-rate Queue、popcount 和 Verilog backend
  • examples/memory 只保留 DMA 和动态 Memory Array 两个 canonical 示例
  • schema、opcode catalog、binding fingerprint、component fingerprint 和 golden 均已重新生成

验证结果

已完成:

  • repository contract 检查
  • IR coverage 检查
  • release-layout 与 NDF 检查
  • Python frontend memory-array 正例与负例
  • ACIR array/invoke verifier 测试
  • QueueGraph plan 测试:24/24
  • gfsim 测试:215/215
  • binding 测试:29/29
  • clang-format 全仓检查
  • Memory Array 与 DMA 示例生成、编译和运行

本地尚未安装完整 lit helper、Verilator 和外部 pinned PYC toolchain,因此这些可选工具对应的完整集成测试由 CI 或具备完整工具链的环境执行。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant