Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2063559
feat: add 4-tap FIR feed-forward filter with true RTL simulation
Feb 10, 2026
2171f78
Merge pull request #1 from zhoubot/codex/hengliao-sync
hengliao1972 Feb 10, 2026
31b8fd5
chore: add .DS_Store, .pdf, .dSYM to .gitignore
Feb 10, 2026
418f3a1
janus/tmu: add TMU ring interconnect implementation, testbenches and …
Feb 10, 2026
7ef1cbb
janus/tmu: add TMU build scripts and visualization tools
Feb 10, 2026
b03ff91
Merge pull request #4 from sheyuheng/tmu-impl-and-spec-hengliao
hengliao1972 Feb 10, 2026
368b8f5
Merge pull request #2 from fengzhazha/cube-accelerator
hengliao1972 Feb 10, 2026
ea79aa1
Add traffic lights pyCircuit example
Auyuir Feb 10, 2026
b5fc5da
Fix traffic lights countdown and add debug
Auyuir Feb 10, 2026
d129cad
Improve traffic lights visualization
Auyuir Feb 10, 2026
db8d434
Add dodgeball game pycircuit demo
Auyuir Feb 10, 2026
deeb190
Merge pull request #5 from Auyuir/traffic-lights-ce-pyc
hengliao1972 Feb 11, 2026
5916583
feat: add BF16 FMAC with 4-stage pipeline from primitive standard cells
Feb 11, 2026
99c36b2
perf: reduce FMAC Stage 2 critical path from 46 to 28
Feb 11, 2026
f259f8d
perf: split multiplier across pipeline stages for better balance
Feb 11, 2026
036254b
feat: add FM16 system — 16 NPU full-mesh simulation with statistics
Feb 12, 2026
97d0fad
feat: FM16 vs SW16 side-by-side topology comparison
Feb 12, 2026
9ea4a6d
fix: show per-NPU bandwidth + SW16 bottleneck analysis
Feb 12, 2026
c4d1e3b
fix: model SW5809s as 512×512 link / 128×128 port crossbar
Feb 12, 2026
2ddcade
feat: model per-input-port independent ECMP RR and VOQ collision
Feb 12, 2026
e00f861
fix: SW5809s arbiter serves 1 pkt/egress-port/cycle (not 4)
Feb 12, 2026
b0773cb
feat: add VOQ depth statistics to ECMP collision analysis
Feb 12, 2026
636115c
Merge PR #6: Enhanced pyCircuit simulation/verification capability
Feb 25, 2026
83f0cdf
examples/fm16: sync fm16 updates (sw5809s.py)
Feb 25, 2026
bd7098d
Merge branch 'LinxISA:main' into main
hengliao1972 Mar 26, 2026
b07f034
fix(examples): put __future__ imports first in emulate scripts
Mar 26, 2026
81ca7d9
feat: migrate all designs and testbenches to PyCircuit V5 cycle-aware…
Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ __pycache__/
*.py[codz]
*$py.class

# macOS
.DS_Store

# C extensions
*.so
*.dylib
*.dSYM/

# Generated PDFs (schematics, etc.)
*.pdf

# Distribution / packaging
.Python
Expand Down
31 changes: 30 additions & 1 deletion compiler/frontend/pycircuit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from . import ct
from . import hierarchical
from . import lib
from . import logic
from . import spec
from . import wiring
Expand All @@ -17,6 +16,23 @@
from .hw import Bundle, Circuit, ClockDomain, Pop, Reg, Vec, Wire, cat, unsigned
from .jit import JitError, compile
from .literals import LiteralValue, S, U, s, u
from .v5 import (
CycleAwareCircuit,
CycleAwareDomain,
CycleAwareSignal,
CycleAwareTb,
StateSignal,
cas,
compile_cycle_aware,
log,
mux,
pyc_CircuitLogger,
pyc_CircuitModule,
pyc_ClockDomain,
pyc_Signal,
signal,
)
from . import lib
from .probe import ProbeBuilder, ProbeError, ProbeRef, ProbeView, TbProbeHandle, TbProbes
from .tb import Tb, sva
from .testbench import TestbenchProgram
Expand All @@ -25,6 +41,19 @@
probe = _probe_decorator

__all__ = [
"CycleAwareCircuit",
"CycleAwareDomain",
"CycleAwareSignal",
"CycleAwareTb",
"cas",
"compile_cycle_aware",
"log",
"mux",
"pyc_CircuitLogger",
"pyc_CircuitModule",
"pyc_ClockDomain",
"pyc_Signal",
"signal",
"Connector",
"ConnectorBundle",
"ConnectorStruct",
Expand Down
7 changes: 7 additions & 0 deletions compiler/frontend/pycircuit/hw.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,13 @@ def scope(self, name: str) -> Iterator[None]:
def domain(self, name: str) -> ClockDomain:
return ClockDomain(clk=self.clock(f"{name}_clk"), rst=self.reset(f"{name}_rst"))

def create_domain(self, name: str, *, frequency_desc: str = "", reset_active_high: bool = False) -> Any:
"""V5 cycle-aware domain (next/prev/push/pop); see `pycircuit.v5.CycleAwareDomain`."""
from .v5 import CycleAwareDomain

_ = (frequency_desc, reset_active_high)
return CycleAwareDomain(self, str(name))

def input(self, name: str, *, width: int, signed: bool = False) -> Wire: # type: ignore[override]
"""Declare a module input port and return it as a `Wire`."""
return Wire(self, super().input(name, width=width), signed=bool(signed))
Expand Down
17 changes: 13 additions & 4 deletions compiler/frontend/pycircuit/jit_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,24 @@ def get_function_meta(fn: Any, *, fn_name: str | None = None) -> FunctionMeta:
if cached is not None and (fn_name is None or cached.fdef.name == fn_name):
return cached

lines, start_line = inspect.getsourcelines(fn)
source = textwrap.dedent("".join(lines))
tree = ast.parse(source)
synthetic = getattr(fn, "__pycircuit_jit_source__", None)
if isinstance(synthetic, str) and synthetic.strip():
source = textwrap.dedent(synthetic).strip() + "\n"
start_line = int(getattr(fn, "__pycircuit_jit_start_line__", 1) or 1)
tree = ast.parse(source)
else:
lines, start_line = inspect.getsourcelines(fn)
source = textwrap.dedent("".join(lines))
tree = ast.parse(source)
name = fn_name if fn_name is not None else getattr(fn, "__name__", None)
if not isinstance(name, str) or not name:
raise RuntimeError(f"failed to infer function name for {fn!r}")
fdef = _find_function_def(tree, name)

source_file = inspect.getsourcefile(fn) or inspect.getfile(fn)
if isinstance(synthetic, str) and synthetic.strip():
source_file = getattr(fn, "__pycircuit_jit_source_file__", None) or "<pycircuit_v5>"
else:
source_file = inspect.getsourcefile(fn) or inspect.getfile(fn)
source_stem = None
try:
if source_file:
Expand Down
61 changes: 26 additions & 35 deletions compiler/frontend/pycircuit/lib/cache.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
from __future__ import annotations

from ..connectors import Connector, ConnectorBundle
from ..design import module
from ..dsl import Signal
from ..hw import Circuit
from ..literals import u
from pycircuit.hw import Circuit, ClockDomain, Wire
from pycircuit.literals import u


@module(structural=True)
def Cache(
m: Circuit,
clk: Connector,
rst: Connector,
req_valid: Connector,
req_addr: Connector,
req_write: Connector,
req_wdata: Connector,
req_wmask: Connector,
cd: ClockDomain,
req_valid: Wire,
req_addr: Wire,
req_write: Wire,
req_wdata: Wire,
req_wmask: Wire,
*,
ways: int = 4,
sets: int = 64,
Expand All @@ -26,7 +21,7 @@ def Cache(
write_back: bool = True,
write_allocate: bool = True,
replacement: str = "plru",
) -> ConnectorBundle:
):
"""Structural cache baseline.

Default policy contract:
Expand All @@ -39,31 +34,27 @@ def Cache(
"""

_ = (line_bytes, write_back, write_allocate, replacement)
clk_v = clk.read() if isinstance(clk, Connector) else clk
rst_v = rst.read() if isinstance(rst, Connector) else rst

req_valid_v = req_valid.read() if isinstance(req_valid, Connector) else req_valid
req_addr_v = req_addr.read() if isinstance(req_addr, Connector) else req_addr
req_write_v = req_write.read() if isinstance(req_write, Connector) else req_write
req_wdata_v = req_wdata.read() if isinstance(req_wdata, Connector) else req_wdata
req_wmask_v = req_wmask.read() if isinstance(req_wmask, Connector) else req_wmask
req_valid_w = m.wire(req_valid_v) if isinstance(req_valid_v, Signal) else req_valid_v
req_addr_w = m.wire(req_addr_v) if isinstance(req_addr_v, Signal) else req_addr_v
req_write_w = m.wire(req_write_v) if isinstance(req_write_v, Signal) else req_write_v
req_wdata_w = m.wire(req_wdata_v) if isinstance(req_wdata_v, Signal) else req_wdata_v
_req_wmask_w = m.wire(req_wmask_v) if isinstance(req_wmask_v, Signal) else req_wmask_v
clk_v = cd.clk
rst_v = cd.rst

req_valid_w = req_valid
req_addr_w = req_addr
req_write_w = req_write
req_wdata_w = req_wdata
_req_wmask_w = req_wmask
_ = _req_wmask_w
ways_i = max(1, int(ways))
sets_i = max(1, int(sets))
set_bits = max(1, (sets_i - 1).bit_length())
tag_bits = max(1, int(addr_width) - set_bits)
plru_bits = max(1, ways_i - 1)
way_idx_bits = max(1, (ways_i - 1).bit_length())

tags = [m.out(f"cache_tag_{i}", clk=clk_v, rst=rst_v, width=tag_bits, init=0) for i in range(ways_i)]
valids = [m.out(f"cache_valid_{i}", clk=clk_v, rst=rst_v, width=1, init=0) for i in range(ways_i)]
dirty = [m.out(f"cache_dirty_{i}", clk=clk_v, rst=rst_v, width=1, init=0) for i in range(ways_i)]
data = [m.out(f"cache_data_{i}", clk=clk_v, rst=rst_v, width=int(data_width), init=0) for i in range(ways_i)]
plru = m.out("cache_plru", clk=clk_v, rst=rst_v, width=plru_bits, init=0)
tags = [m.out(f"cache_tag_{i}", domain=cd, width=tag_bits, init=0) for i in range(ways_i)]
valids = [m.out(f"cache_valid_{i}", domain=cd, width=1, init=0) for i in range(ways_i)]
dirty = [m.out(f"cache_dirty_{i}", domain=cd, width=1, init=0) for i in range(ways_i)]
data = [m.out(f"cache_data_{i}", domain=cd, width=int(data_width), init=0) for i in range(ways_i)]
plru = m.out("cache_plru", domain=cd, width=plru_bits, init=0)

req_tag = req_addr_w[set_bits : set_bits + tag_bits]

Expand All @@ -73,8 +64,8 @@ def Cache(

for i in range(ways_i):
way_hit = valids[i].out() & (tags[i].out() == req_tag)
hit_data = data[i].out() if way_hit else hit_data
hit_way = i if way_hit else hit_way
hit_data = way_hit._select_internal(data[i].out(), hit_data)
hit_way = way_hit._select_internal(u(way_idx_bits, i), hit_way)
hit = hit | way_hit

victim_way = plru.out()[0:way_idx_bits]
Expand All @@ -101,7 +92,7 @@ def Cache(
resp_valid = req_valid_w
resp_ready = req_valid_w
resp_hit = hit
resp_data = hit_data if hit else u(int(data_width), 0)
resp_data = hit._select_internal(hit_data, u(int(data_width), 0))
miss = req_valid_w & (~hit)

return m.bundle_connector(
Expand Down
64 changes: 29 additions & 35 deletions compiler/frontend/pycircuit/lib/mem2port.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,44 @@
from __future__ import annotations

from ..connectors import Connector, ConnectorBundle, ConnectorError
from ..design import module
from ..dsl import Signal
from ..hw import Circuit
from pycircuit.dsl import Signal
from pycircuit.hw import Circuit, ClockDomain, Wire


class Mem2PortError(ValueError):
pass


@module(structural=True)
def Mem2Port(
m: Circuit,
clk: Connector,
rst: Connector,
ren0: Connector,
raddr0: Connector,
ren1: Connector,
raddr1: Connector,
wvalid: Connector,
waddr: Connector,
wdata: Connector,
wstrb: Connector,
cd: ClockDomain,
ren0: Wire,
raddr0: Wire,
ren1: Wire,
raddr1: Wire,
wvalid: Wire,
waddr: Wire,
wdata: Wire,
wstrb: Wire,
*,
depth: int,
) -> ConnectorBundle:
clk_v = clk.read() if isinstance(clk, Connector) else clk
rst_v = rst.read() if isinstance(rst, Connector) else rst
):
clk_v = cd.clk
rst_v = cd.rst
if not isinstance(clk_v, Signal) or clk_v.ty != "!pyc.clock":
raise ConnectorError("Mem2Port.clk must be !pyc.clock")
raise Mem2PortError("Mem2Port domain clk must be !pyc.clock")
if not isinstance(rst_v, Signal) or rst_v.ty != "!pyc.reset":
raise ConnectorError("Mem2Port.rst must be !pyc.reset")

def wire_of(v):
vv = v.read() if isinstance(v, Connector) else v
if isinstance(vv, Signal):
return m.wire(vv)
return vv
raise Mem2PortError("Mem2Port domain rst must be !pyc.reset")

ren0_w = wire_of(ren0)
ren1_w = wire_of(ren1)
wvalid_w = wire_of(wvalid)
raddr0_w = wire_of(raddr0)
raddr1_w = wire_of(raddr1)
waddr_w = wire_of(waddr)
wdata_w = wire_of(wdata)
wstrb_w = wire_of(wstrb)
ren0_w = ren0
ren1_w = ren1
wvalid_w = wvalid
raddr0_w = raddr0
raddr1_w = raddr1
waddr_w = waddr
wdata_w = wdata
wstrb_w = wstrb
if ren0_w.ty != "i1" or ren1_w.ty != "i1" or wvalid_w.ty != "i1":
raise ConnectorError("Mem2Port ren0/ren1/wvalid must be i1")
raise Mem2PortError("Mem2Port ren0/ren1/wvalid must be i1")

rdata0, rdata1 = m.sync_mem_dp(
clk_v,
Expand Down
24 changes: 8 additions & 16 deletions compiler/frontend/pycircuit/lib/picker.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
from __future__ import annotations

from ..connectors import Connector, ConnectorBundle, ConnectorError
from ..design import module
from ..dsl import Signal
from ..hw import Circuit
from ..literals import u
from pycircuit.hw import Circuit, Wire
from pycircuit.literals import u


@module(structural=True)
def Picker(
m: Circuit,
req: Connector,
req: Wire,
*,
width: int | None = None,
) -> ConnectorBundle:
req_v = req.read() if isinstance(req, Connector) else req
if isinstance(req_v, Signal):
req_w = m.wire(req_v)
else:
req_w = req_v
):
req_w = req
if not hasattr(req_w, "ty") or not str(req_w.ty).startswith("i"):
raise ConnectorError("Picker.req must be an integer wire connector")
raise ValueError("Picker.req must be an integer wire")
w = int(width) if width is not None else int(req_w.width)
if w <= 0:
raise ValueError("Picker width must be > 0")
Expand All @@ -32,8 +24,8 @@ def Picker(

for i in range(w):
take = req_w[i] & ~found
grant = u(w, 1 << i) if take else grant
index = u(idx_w, i) if take else index
grant = take._select_internal(u(w, 1 << i), grant)
index = take._select_internal(u(idx_w, i), index)
found = found | req_w[i]

return m.bundle_connector(
Expand Down
Loading
Loading