diff --git a/asm/__init__.py b/asm/__init__.py index e589719..9a3261d 100644 --- a/asm/__init__.py +++ b/asm/__init__.py @@ -16,12 +16,26 @@ from asm.expand import expand from asm.resolve import resolve from asm.place import place from asm.allocate import allocate -from asm.codegen import generate_direct, generate_tokens, AssemblyResult +# TODO: Phase 6 - Fix codegen for frame model +# from asm.codegen import generate_direct, generate_tokens, AssemblyResult from asm.errors import ErrorSeverity, format_error from asm.serialize import serialize as _serialize_graph from asm.ir import IRGraph from asm.builtins import BUILTIN_MACROS, _BUILTIN_LINE_COUNT +# Stub implementations for Phase 6 codegen +class AssemblyResult: + """Stub for Phase 6 codegen rewrite.""" + pass + +def generate_direct(graph): + """Stub for Phase 6 codegen rewrite.""" + raise NotImplementedError("Phase 6: Codegen rewrite not yet implemented") + +def generate_tokens(graph): + """Stub for Phase 6 codegen rewrite.""" + raise NotImplementedError("Phase 6: Codegen rewrite not yet implemented") + _GRAMMAR_PATH = Path(__file__).parent.parent / "dfasm.lark" _parser = None diff --git a/asm/allocate.py b/asm/allocate.py index 54a22ce..1a77eb2 100644 --- a/asm/allocate.py +++ b/asm/allocate.py @@ -14,7 +14,7 @@ from collections import defaultdict from asm.errors import AssemblyError, ErrorCategory, ErrorSeverity from asm.ir import IRGraph, IRNode, IREdge, SourceLoc, ResolvedDest, CallSite, collect_all_nodes_and_edges, update_graph_nodes from asm.opcodes import is_dyadic, is_monadic -from cm_inst import Addr, ArithOp, LogicOp, MemOp, Port, RoutingOp +from cm_inst import ArithOp, LogicOp, MemOp, Port, RoutingOp diff --git a/asm/codegen.py b/asm/codegen.py index 85ae92b..bd9f1c1 100644 --- a/asm/codegen.py +++ b/asm/codegen.py @@ -14,7 +14,7 @@ from collections import defaultdict from asm.errors import AssemblyError, ErrorCategory from asm.ir import ( IRGraph, IRNode, IREdge, ResolvedDest, collect_all_nodes_and_edges, collect_all_data_defs, - DEFAULT_IRAM_CAPACITY, DEFAULT_CTX_SLOTS + DEFAULT_IRAM_CAPACITY, DEFAULT_FRAME_COUNT ) from asm.opcodes import is_dyadic from cm_inst import ALUInst, MemOp, Port, RoutingOp, SMInst @@ -221,8 +221,7 @@ def generate_direct(graph: IRGraph) -> AssemblyResult: config = PEConfig( pe_id=pe_id, iram=iram, - ctx_slots=graph.system.ctx_slots if graph.system else DEFAULT_CTX_SLOTS, - offsets=graph.system.iram_capacity if graph.system else DEFAULT_IRAM_CAPACITY, + frame_count=graph.system.frame_count if graph.system else DEFAULT_FRAME_COUNT, allowed_pe_routes=allowed_pe_routes, allowed_sm_routes=allowed_sm_routes, ) diff --git a/asm/expand.py b/asm/expand.py index 3d1f8f2..a65f60d 100644 --- a/asm/expand.py +++ b/asm/expand.py @@ -20,7 +20,7 @@ from asm.errors import AssemblyError, ErrorCategory from asm.ir import ( IRGraph, IRNode, IREdge, IRRegion, RegionKind, ParamRef, ConstExpr, MacroDef, IRMacroCall, CallSiteResult, CallSite, IRRepetitionBlock, SourceLoc, - PlacementRef, PortRef, CtxSlotRef, CtxSlotRange, + PlacementRef, PortRef, ActSlotRef, ActSlotRange, ) from asm.opcodes import MNEMONIC_TO_OP from cm_inst import Port, RoutingOp @@ -355,12 +355,12 @@ def _clone_and_substitute_node( )) new_pe = None - # Resolve ctx_slot if it's a CtxSlotRef + # Resolve ctx_slot if it's a ActSlotRef new_ctx_slot = node.ctx_slot - if isinstance(new_ctx_slot, CtxSlotRef): + if isinstance(new_ctx_slot, ActSlotRef): resolved = _substitute_param(new_ctx_slot.param, subst_map) if isinstance(resolved, int): - new_ctx_slot = CtxSlotRange(start=resolved, end=resolved) + new_ctx_slot = ActSlotRange(start=resolved, end=resolved) else: errors.append(AssemblyError( loc=node.loc, diff --git a/asm/ir.py b/asm/ir.py index c6ab59d..3219021 100644 --- a/asm/ir.py +++ b/asm/ir.py @@ -11,14 +11,16 @@ from dataclasses import dataclass, field, replace from enum import Enum from typing import TYPE_CHECKING, Iterator, Optional, Union -from cm_inst import ALUOp, Addr, MemOp, Port +from cm_inst import ALUOp, MemOp, Port if TYPE_CHECKING: from asm.errors import AssemblyError # Default configuration values for system parameters -DEFAULT_IRAM_CAPACITY = 128 -DEFAULT_CTX_SLOTS = 16 +DEFAULT_IRAM_CAPACITY = 256 +DEFAULT_FRAME_COUNT = 8 +DEFAULT_FRAME_SLOTS = 64 +DEFAULT_MATCHABLE_OFFSETS = 8 @dataclass(frozen=True) @@ -72,7 +74,12 @@ class IRNode: const: Optional constant operand (int, ParamRef, or ConstExpr) pe: Optional PE placement qualifier iram_offset: Optional offset in PE's IRAM (populated during allocation) - ctx: Optional context slot (populated during allocation) + act_slot: Optional activation slot (populated during allocation) + act_id: Optional activation ID (populated during allocation) + mode: Optional output mode tuple (OutputStyle, has_const, dest_count) — set by allocate + fref: Optional frame slot base index — set by allocate + wide: Wide operation flag + frame_layout: Optional frame slot map — set by allocate loc: Source location for error reporting args: Optional named arguments dictionary (e.g., {"dest": 0x45}) sm_id: Optional SM ID for MemOp instructions (populated during lowering) @@ -83,9 +90,13 @@ class IRNode: dest_r: Optional[Union[NameRef, ResolvedDest]] = None const: Optional[Union[int, ParamRef, ConstExpr]] = None pe: Optional[Union[int, PlacementRef]] = None - ctx_slot: Optional[Union[int, CtxSlotRef, CtxSlotRange]] = None + act_slot: Optional[Union[int, ActSlotRef, ActSlotRange]] = None iram_offset: Optional[int] = None - ctx: Optional[int] = None + act_id: Optional[int] = None + mode: Optional[tuple] = None + fref: Optional[int] = None + wide: bool = False + frame_layout: Optional[FrameLayout] = None loc: SourceLoc = SourceLoc(0, 0) args: Optional[dict[str, int]] = None sm_id: Optional[int] = None @@ -182,18 +193,46 @@ class PortRef: @dataclass(frozen=True) -class CtxSlotRef: - """Deferred context slot from macro parameter.""" +class ActSlotRef: + """Deferred activation slot from macro parameter.""" param: ParamRef @dataclass(frozen=True) -class CtxSlotRange: - """Explicit context slot range reservation.""" +class ActSlotRange: + """Explicit activation slot range reservation.""" start: int end: int +@dataclass(frozen=True) +class FrameSlotMap: + """Slot map for a frame layout. + + Attributes: + match_slots: Offsets of match operand slots + const_slots: Offsets of constant slots + dest_slots: Offsets of destination slots + sink_slots: Offsets of sink/SM parameter slots + """ + match_slots: tuple[int, ...] + const_slots: tuple[int, ...] + dest_slots: tuple[int, ...] + sink_slots: tuple[int, ...] + + +@dataclass(frozen=True) +class FrameLayout: + """Frame slot layout for an activation. + + Attributes: + slot_map: The frame slot map + total_slots: Total number of slots used + """ + slot_map: FrameSlotMap + total_slots: int + + @dataclass(frozen=True) class IRRepetitionBlock: """A repetition block within a macro body template. @@ -299,14 +338,14 @@ class CallSite: call_id: Unique call site identifier (counter) input_edges: Edge names for cross-context inputs trampoline_nodes: Names of generated trampoline pass nodes - free_ctx_nodes: Names of generated free_ctx nodes + free_frame_nodes: Names of generated free_frame nodes loc: Source location of the call """ func_name: str call_id: int input_edges: tuple[str, ...] = () trampoline_nodes: tuple[str, ...] = () - free_ctx_nodes: tuple[str, ...] = () + free_frame_nodes: tuple[str, ...] = () loc: SourceLoc = SourceLoc(0, 0) @@ -317,14 +356,18 @@ class SystemConfig: Attributes: pe_count: Number of processing elements sm_count: Number of structure memory instances - iram_capacity: IRAM size per PE (default 64) - ctx_slots: Number of context slots per PE (default 4) + iram_capacity: IRAM size per PE (default 256) + frame_count: Number of frames per PE (default 8) + frame_slots: Total slots per frame (default 64) + matchable_offsets: Number of matchable IRAM offsets per frame (default 8) loc: Source location for error reporting """ pe_count: int sm_count: int iram_capacity: int = DEFAULT_IRAM_CAPACITY - ctx_slots: int = DEFAULT_CTX_SLOTS + frame_count: int = DEFAULT_FRAME_COUNT + frame_slots: int = DEFAULT_FRAME_SLOTS + matchable_offsets: int = DEFAULT_MATCHABLE_OFFSETS loc: SourceLoc = SourceLoc(0, 0) diff --git a/asm/lower.py b/asm/lower.py index d18c4f6..52ce4cb 100644 --- a/asm/lower.py +++ b/asm/lower.py @@ -21,7 +21,7 @@ from asm.ir import ( IRGraph, IRNode, IREdge, IRRegion, RegionKind, IRDataDef, SystemConfig, SourceLoc, NameRef, ResolvedDest, MacroParam, ParamRef, MacroDef, IRMacroCall, CallSiteResult, IRRepetitionBlock, - PlacementRef, PortRef, CtxSlotRef, CtxSlotRange, + PlacementRef, PortRef, ActSlotRef, ActSlotRange, ) from asm.errors import AssemblyError, ErrorCategory from asm.opcodes import MNEMONIC_TO_OP @@ -1258,7 +1258,7 @@ class LowerTransformer(Transformer): placement = arg elif isinstance(arg, PortRef): port = arg - elif isinstance(arg, (CtxSlotRef, CtxSlotRange)): + elif isinstance(arg, (ActSlotRef, ActSlotRange)): ctx_slot = arg elif isinstance(arg, (Port, int)): port = arg @@ -1313,22 +1313,22 @@ class LowerTransformer(Transformer): def ctx_slot(self, args: list): """Extract context slot specifier. - Always returns a typed wrapper (CtxSlotRef, CtxSlotRange) so + Always returns a typed wrapper (ActSlotRef, ActSlotRange) so qualified_ref can distinguish ctx_slot ints from port ints. """ if len(args) == 1: arg = args[0] if isinstance(arg, ParamRef): - return CtxSlotRef(param=arg) - if isinstance(arg, CtxSlotRange): + return ActSlotRef(param=arg) + if isinstance(arg, ActSlotRange): return arg n = int(str(arg)) - return CtxSlotRange(start=n, end=n) + return ActSlotRange(start=n, end=n) return args[0] - def ctx_range(self, args: list) -> CtxSlotRange: + def ctx_range(self, args: list) -> ActSlotRange: """Extract context slot range (start..end).""" - return CtxSlotRange(start=int(str(args[0])), end=int(str(args[1]))) + return ActSlotRange(start=int(str(args[0])), end=int(str(args[1]))) @v_args(inline=True) def port(self, token) -> Union[Port, int, PortRef]: diff --git a/asm/opcodes.py b/asm/opcodes.py index 06787a8..26eda67 100644 --- a/asm/opcodes.py +++ b/asm/opcodes.py @@ -18,9 +18,9 @@ MNEMONIC_TO_OP: dict[str, Union[ArithOp, LogicOp, RoutingOp, MemOp]] = { "sub": ArithOp.SUB, "inc": ArithOp.INC, "dec": ArithOp.DEC, - "shiftl": ArithOp.SHIFT_L, - "shiftr": ArithOp.SHIFT_R, - "ashiftr": ArithOp.ASHFT_R, + "shl": ArithOp.SHL, + "shr": ArithOp.SHR, + "asr": ArithOp.ASR, # Logic operations "and": LogicOp.AND, "or": LogicOp.OR, @@ -45,7 +45,9 @@ MNEMONIC_TO_OP: dict[str, Union[ArithOp, LogicOp, RoutingOp, MemOp]] = { "merge": RoutingOp.MRGE, "pass": RoutingOp.PASS, "const": RoutingOp.CONST, - "free_ctx": RoutingOp.FREE_CTX, # ALU free (deallocate context slot) + "free_frame": RoutingOp.FREE_FRAME, # ALU free (deallocate frame slot) + "extract_tag": RoutingOp.EXTRACT_TAG, + "alloc_remote": RoutingOp.ALLOC_REMOTE, # Memory operations "read": MemOp.READ, "write": MemOp.WRITE, @@ -136,15 +138,17 @@ _MONADIC_OPS_TUPLES: frozenset[tuple[type, int]] = frozenset([ # are only reached via `op in MONADIC_OPS` (TypeAwareMonadicOpsSet). (ArithOp, int(ArithOp.INC)), (ArithOp, int(ArithOp.DEC)), - (ArithOp, int(ArithOp.SHIFT_L)), - (ArithOp, int(ArithOp.SHIFT_R)), - (ArithOp, int(ArithOp.ASHFT_R)), + (ArithOp, int(ArithOp.SHL)), + (ArithOp, int(ArithOp.SHR)), + (ArithOp, int(ArithOp.ASR)), # Logic: single input (LogicOp, int(LogicOp.NOT)), # Routing: single input or no ALU involvement (RoutingOp, int(RoutingOp.PASS)), (RoutingOp, int(RoutingOp.CONST)), - (RoutingOp, int(RoutingOp.FREE_CTX)), + (RoutingOp, int(RoutingOp.FREE_FRAME)), + (RoutingOp, int(RoutingOp.EXTRACT_TAG)), + (RoutingOp, int(RoutingOp.ALLOC_REMOTE)), # Memory: single input (monadic SM operations) (MemOp, int(MemOp.READ)), (MemOp, int(MemOp.ALLOC)), diff --git a/asm/place.py b/asm/place.py index 0fd40f7..3f93a10 100644 --- a/asm/place.py +++ b/asm/place.py @@ -15,7 +15,7 @@ from dataclasses import replace from asm.errors import AssemblyError, ErrorCategory from asm.ir import ( IRGraph, IRNode, IRRegion, RegionKind, SystemConfig, SourceLoc, collect_all_nodes, - update_graph_nodes, DEFAULT_IRAM_CAPACITY, DEFAULT_CTX_SLOTS + update_graph_nodes, DEFAULT_IRAM_CAPACITY, DEFAULT_FRAME_COUNT ) from asm.opcodes import is_dyadic @@ -56,7 +56,7 @@ def _infer_system_config(graph: IRGraph) -> SystemConfig: pe_count=pe_count, sm_count=1, # Default to 1 SM iram_capacity=DEFAULT_IRAM_CAPACITY, - ctx_slots=DEFAULT_CTX_SLOTS, + frame_count=DEFAULT_FRAME_COUNT, loc=SourceLoc(0, 0), ) diff --git a/dfasm.lark b/dfasm.lark index 443cd88..d13058f 100644 --- a/dfasm.lark +++ b/dfasm.lark @@ -142,13 +142,13 @@ call_output: IDENT "=" qualified_ref -> named_output opcode: OPCODE | param_ref OPCODE.2: "add" | "sub" | "inc" | "dec" - | "shiftl" | "shiftr" | "ashiftr" + | "shl" | "shr" | "asr" | "and" | "or" | "xor" | "not" | "eq" | "lt" | "lte" | "gt" | "gte" | "breq" | "brgt" | "brge" | "brof" | "brty" | "sweq" | "swgt" | "swge" | "swof" | "swty" | "gate" | "sel" | "merge" - | "pass" | "const" | "free_ctx" | "change_tag" | "extract_tag" + | "pass" | "const" | "free_frame" | "extract_tag" | "alloc_remote" | "read" | "write" | "clear" | "exec" | "alloc" | "free" | "rd_inc" | "rd_dec" | "cmp_sw" | "ior" | "iow" | "iorw" | "load_inst" | "route_set" diff --git a/tests/test_ir_frame_types.py b/tests/test_ir_frame_types.py new file mode 100644 index 0000000..f0615fb --- /dev/null +++ b/tests/test_ir_frame_types.py @@ -0,0 +1,201 @@ +"""Tests for IR frame model types (Phase 4, Task 1). + +Verifies: +- IRNode has act_id and act_slot fields (not ctx) +- IRNode has mode, fref, wide, frame_layout fields +- SystemConfig has frame_count, frame_slots, matchable_offsets (not ctx_slots) +- ActSlotRef and ActSlotRange exist +- FrameSlotMap and FrameLayout are frozen dataclasses +""" + +import pytest +from dataclasses import fields +import sys +import os + +# Add the project root to the path +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +# Import ir.py directly to avoid codegen import errors +from asm import ir + + +class TestIRNodeFields: + """Test IRNode frame model fields.""" + + def test_irnode_has_act_id_field(self): + """IRNode should have act_id field, not ctx.""" + node_fields = {f.name for f in fields(ir.IRNode)} + assert "act_id" in node_fields + assert "ctx" not in node_fields + + def test_irnode_has_act_slot_field(self): + """IRNode should have act_slot field, not ctx_slot.""" + node_fields = {f.name for f in fields(ir.IRNode)} + assert "act_slot" in node_fields + assert "ctx_slot" not in node_fields + + def test_irnode_has_frame_model_fields(self): + """IRNode should have mode, fref, wide, frame_layout fields.""" + node_fields = {f.name for f in fields(ir.IRNode)} + assert "mode" in node_fields + assert "fref" in node_fields + assert "wide" in node_fields + assert "frame_layout" in node_fields + + def test_irnode_wide_defaults_to_false(self): + """IRNode.wide should default to False.""" + node_fields = {f.name: f for f in fields(ir.IRNode)} + assert node_fields["wide"].default is False + + def test_irnode_mode_defaults_to_none(self): + """IRNode.mode should default to None.""" + node_fields = {f.name: f for f in fields(ir.IRNode)} + assert node_fields["mode"].default is None + + def test_irnode_fref_defaults_to_none(self): + """IRNode.fref should default to None.""" + node_fields = {f.name: f for f in fields(ir.IRNode)} + assert node_fields["fref"].default is None + + def test_irnode_frame_layout_defaults_to_none(self): + """IRNode.frame_layout should default to None.""" + node_fields = {f.name: f for f in fields(ir.IRNode)} + assert node_fields["frame_layout"].default is None + + +class TestSystemConfigFields: + """Test SystemConfig frame model fields.""" + + def test_systemconfig_has_frame_count(self): + """SystemConfig should have frame_count field.""" + config_fields = {f.name for f in fields(ir.SystemConfig)} + assert "frame_count" in config_fields + assert "ctx_slots" not in config_fields + + def test_systemconfig_has_frame_slots(self): + """SystemConfig should have frame_slots field.""" + config_fields = {f.name for f in fields(ir.SystemConfig)} + assert "frame_slots" in config_fields + + def test_systemconfig_has_matchable_offsets(self): + """SystemConfig should have matchable_offsets field.""" + config_fields = {f.name for f in fields(ir.SystemConfig)} + assert "matchable_offsets" in config_fields + + def test_systemconfig_defaults_correct(self): + """SystemConfig should have correct default values.""" + config = ir.SystemConfig(pe_count=2, sm_count=1) + assert config.iram_capacity == 256 + assert config.frame_count == 8 + assert config.frame_slots == 64 + assert config.matchable_offsets == 8 + + def test_systemconfig_iram_capacity_default(self): + """SystemConfig iram_capacity should default to 256.""" + assert ir.DEFAULT_IRAM_CAPACITY == 256 + + def test_systemconfig_frame_count_default(self): + """DEFAULT_FRAME_COUNT should be 8.""" + assert ir.DEFAULT_FRAME_COUNT == 8 + + def test_systemconfig_frame_slots_default(self): + """DEFAULT_FRAME_SLOTS should be 64.""" + assert ir.DEFAULT_FRAME_SLOTS == 64 + + def test_systemconfig_matchable_offsets_default(self): + """DEFAULT_MATCHABLE_OFFSETS should be 8.""" + assert ir.DEFAULT_MATCHABLE_OFFSETS == 8 + + +class TestActSlotTypes: + """Test ActSlotRef and ActSlotRange types.""" + + def test_actslotref_exists(self): + """ActSlotRef should exist and be a frozen dataclass.""" + ref = ir.ActSlotRef(ir.ParamRef("param")) + assert ref.param.param == "param" + + def test_actslotrange_exists(self): + """ActSlotRange should exist and be a frozen dataclass.""" + range_obj = ir.ActSlotRange(0, 8) + assert range_obj.start == 0 + assert range_obj.end == 8 + + def test_actslotref_frozen(self): + """ActSlotRef should be frozen.""" + ref = ir.ActSlotRef(ir.ParamRef("param")) + with pytest.raises(Exception): # FrozenInstanceError or AttributeError + ref.param = ir.ParamRef("other") + + def test_actslotrange_frozen(self): + """ActSlotRange should be frozen.""" + range_obj = ir.ActSlotRange(0, 8) + with pytest.raises(Exception): + range_obj.start = 1 + + +class TestFrameSlotMap: + """Test FrameSlotMap type.""" + + def test_frameslotmap_frozen_dataclass(self): + """FrameSlotMap should be a frozen dataclass.""" + slot_map = ir.FrameSlotMap( + match_slots=(0, 1), + const_slots=(2,), + dest_slots=(3, 4), + sink_slots=(5,), + ) + assert slot_map.match_slots == (0, 1) + assert slot_map.const_slots == (2,) + assert slot_map.dest_slots == (3, 4) + assert slot_map.sink_slots == (5,) + + def test_frameslotmap_frozen(self): + """FrameSlotMap should be frozen.""" + slot_map = ir.FrameSlotMap( + match_slots=(0, 1), + const_slots=(2,), + dest_slots=(3, 4), + sink_slots=(5,), + ) + with pytest.raises(Exception): + slot_map.match_slots = (0, 2) + + +class TestFrameLayout: + """Test FrameLayout type.""" + + def test_framelayout_frozen_dataclass(self): + """FrameLayout should be a frozen dataclass.""" + slot_map = ir.FrameSlotMap( + match_slots=(0, 1), + const_slots=(2,), + dest_slots=(3, 4), + sink_slots=(5,), + ) + layout = ir.FrameLayout(slot_map=slot_map, total_slots=6) + assert layout.slot_map == slot_map + assert layout.total_slots == 6 + + def test_framelayout_frozen(self): + """FrameLayout should be frozen.""" + slot_map = ir.FrameSlotMap( + match_slots=(0, 1), + const_slots=(2,), + dest_slots=(3, 4), + sink_slots=(5,), + ) + layout = ir.FrameLayout(slot_map=slot_map, total_slots=6) + with pytest.raises(Exception): + layout.total_slots = 7 + + +class TestCallSiteFields: + """Test CallSite frame model field renames.""" + + def test_callsite_has_free_frame_nodes(self): + """CallSite should have free_frame_nodes field, not free_ctx_nodes.""" + call_site_fields = {f.name for f in fields(ir.CallSite)} + assert "free_frame_nodes" in call_site_fields + assert "free_ctx_nodes" not in call_site_fields