OR-1 dataflow CPU sketch
at main 312 lines 11 kB view raw
1"""Boundary functions for pack/unpack between semantic types and 16-bit hardware words. 2 3This module encodes/decodes tokens and instructions using the exact hardware wire format 4from architecture-overview.md and design-notes/alu-and-output-design.md. 5 6Instruction word format: [type:1][opcode:5][mode:3][wide:1][fref:6] 7Flit 1 (routing/header flit) format depends on token kind (DYADIC, MONADIC, INLINE, SM). 8""" 9 10from __future__ import annotations 11 12from cm_inst import ( 13 ALUOp, ArithOp, FrameDest, Instruction, LogicOp, MemOp, 14 OutputStyle, Port, RoutingOp, TokenKind, 15) 16from tokens import ( 17 CMToken, DyadToken, FrameControlToken, MonadToken, 18 PELocalWriteToken, PEToken, SMToken, Token, 19) 20 21 22def _encode_mode(output: OutputStyle, has_const: bool, dest_count: int) -> int: 23 """Encode OutputStyle + has_const + dest_count into 3-bit mode field. 24 25 Follows the mode table from design-notes/alu-and-output-design.md. 26 27 Validation: 28 - INHERIT requires dest_count 1 or 2 29 - CHANGE_TAG requires dest_count == 1 (dynamic routing from left operand) 30 - SINK requires dest_count == 0 (no output routing) 31 """ 32 const_bit = int(has_const) 33 if output == OutputStyle.INHERIT: 34 if dest_count == 1: 35 return 0b000 | const_bit # mode 0 or 1 36 elif dest_count == 2: 37 return 0b010 | const_bit # mode 2 or 3 38 raise ValueError(f"INHERIT requires dest_count 1 or 2, got {dest_count}") 39 elif output == OutputStyle.CHANGE_TAG: 40 if dest_count != 1: 41 raise ValueError(f"CHANGE_TAG requires dest_count == 1, got {dest_count}") 42 return 0b100 | const_bit # mode 4 or 5 43 elif output == OutputStyle.SINK: 44 if dest_count != 0: 45 raise ValueError(f"SINK requires dest_count == 0, got {dest_count}") 46 return 0b110 | const_bit # mode 6 or 7 47 raise ValueError(f"Unknown OutputStyle: {output}") 48 49 50def _decode_mode(mode: int) -> tuple[OutputStyle, bool, int]: 51 """Decode 3-bit mode field into (OutputStyle, has_const, dest_count).""" 52 has_const = bool(mode & 0b001) 53 if not (mode & 0b100): 54 # modes 0-3: INHERIT 55 dest_count = 2 if (mode & 0b010) else 1 56 return OutputStyle.INHERIT, has_const, dest_count 57 elif not (mode & 0b010): 58 # modes 4-5: CHANGE_TAG 59 # dest_count=1 is nominal — CHANGE_TAG reads destination from the left 60 # operand (packed flit 1), not from frame slots. The PE ignores dest_count 61 # for CHANGE_TAG; this value exists only for round-trip consistency. 62 return OutputStyle.CHANGE_TAG, has_const, 1 63 else: 64 # modes 6-7: SINK 65 return OutputStyle.SINK, has_const, 0 66 67 68def _encode_opcode(opcode: ALUOp | MemOp) -> tuple[int, int]: 69 """Return (type_bit, 5-bit opcode). 70 71 Python IntEnum values match hardware encoding directly. 72 MemOp uses type_bit=1 with its own independent 5-bit opcode space. 73 ALUOp (ArithOp, LogicOp, RoutingOp) uses type_bit=0. 74 """ 75 if isinstance(opcode, MemOp): 76 return 1, int(opcode) & 0x1F 77 return 0, int(opcode) & 0x1F 78 79 80def _decode_opcode(type_bit: int, raw_opcode: int) -> ALUOp | MemOp: 81 """Decode type_bit + 5-bit opcode into Python enum.""" 82 if type_bit: 83 return MemOp(raw_opcode) 84 for cls in (ArithOp, LogicOp, RoutingOp): 85 try: 86 return cls(raw_opcode) 87 except ValueError: 88 continue 89 raise ValueError(f"Unknown ALU opcode: {raw_opcode}") 90 91 92def pack_instruction(inst: Instruction) -> int: 93 """Convert semantic Instruction to 16-bit hardware word. 94 95 Format: [type:1][opcode:5][mode:3][wide:1][fref:6] 96 """ 97 type_bit, opcode_bits = _encode_opcode(inst.opcode) 98 mode_bits = _encode_mode(inst.output, inst.has_const, inst.dest_count) 99 wide_bit = int(inst.wide) 100 fref_bits = inst.fref & 0x3F 101 return (type_bit << 15) | (opcode_bits << 10) | (mode_bits << 7) | (wide_bit << 6) | fref_bits 102 103 104def unpack_instruction(word: int) -> Instruction: 105 """Convert 16-bit hardware word to semantic Instruction.""" 106 type_bit = (word >> 15) & 1 107 opcode_raw = (word >> 10) & 0x1F 108 mode = (word >> 7) & 0x07 109 wide = bool((word >> 6) & 1) 110 fref = word & 0x3F 111 112 opcode = _decode_opcode(type_bit, opcode_raw) 113 output, has_const, dest_count = _decode_mode(mode) 114 return Instruction( 115 opcode=opcode, 116 output=output, 117 has_const=has_const, 118 dest_count=dest_count, 119 wide=wide, 120 fref=fref, 121 ) 122 123 124def pack_flit1(dest: FrameDest) -> int: 125 """Pack structured FrameDest to 16-bit flit 1 value. 126 127 Uses the exact hardware bit layout from architecture-overview.md. 128 129 Format by token_kind: 130 DYADIC: [00][port:1][PE:2][offset:8][act_id:3] = 16 bits 131 MONADIC: [010][PE:2][offset:8][act_id:3] = 16 bits 132 INLINE: [011][PE:2][10][offset:7][spare:2] = 16 bits 133 """ 134 if dest.token_kind == TokenKind.DYADIC: 135 # [00][port:1][PE:2][offset:8][act_id:3] 136 return ( 137 ((dest.port & 0x1) << 13) 138 | ((dest.target_pe & 0x3) << 11) 139 | ((dest.offset & 0xFF) << 3) 140 | (dest.act_id & 0x7) 141 ) 142 elif dest.token_kind == TokenKind.MONADIC: 143 # [010][PE:2][offset:8][act_id:3] 144 return ( 145 (0b010 << 13) 146 | ((dest.target_pe & 0x3) << 11) 147 | ((dest.offset & 0xFF) << 3) 148 | (dest.act_id & 0x7) 149 ) 150 else: 151 # INLINE: [011][PE:2][10][offset:7][spare:2] 152 return ( 153 (0b011 << 13) 154 | ((dest.target_pe & 0x3) << 11) 155 | (0b10 << 9) 156 | ((dest.offset & 0x7F) << 2) 157 ) 158 159 160def unpack_flit1(flit1: int) -> FrameDest: 161 """Unpack 16-bit flit 1 value to structured FrameDest.""" 162 top2 = (flit1 >> 14) & 0x3 163 if top2 == 0b00: 164 # DYADIC WIDE 165 return FrameDest( 166 target_pe=(flit1 >> 11) & 0x3, 167 offset=(flit1 >> 3) & 0xFF, 168 act_id=flit1 & 0x7, 169 port=Port((flit1 >> 13) & 0x1), 170 token_kind=TokenKind.DYADIC, 171 ) 172 elif (flit1 >> 13) == 0b010: 173 # MONADIC NORMAL 174 return FrameDest( 175 target_pe=(flit1 >> 11) & 0x3, 176 offset=(flit1 >> 3) & 0xFF, 177 act_id=flit1 & 0x7, 178 port=Port.L, 179 token_kind=TokenKind.MONADIC, 180 ) 181 else: 182 # MONADIC INLINE: [011][PE:2][10][offset:7][spare:2] 183 return FrameDest( 184 target_pe=(flit1 >> 11) & 0x3, 185 offset=(flit1 >> 2) & 0x7F, 186 act_id=0, 187 port=Port.L, 188 token_kind=TokenKind.INLINE, 189 ) 190 191 192def flit_count(flit1: int) -> int: 193 """Given flit 1 (the first/header flit), return total flit count for this packet.""" 194 if flit1 & 0x8000: 195 # SM token: bit[15]=1. Standard = 2 flits, CAS/EXT = 3. 196 return 2 197 prefix3 = (flit1 >> 13) & 0x7 198 if prefix3 <= 0b001: 199 # Dyadic wide (00x): flit 1 + flit 2 (data) 200 return 2 201 elif prefix3 == 0b010: 202 # Monadic normal: flit 1 + flit 2 (data) 203 return 2 204 elif prefix3 == 0b011: 205 sub = (flit1 >> 9) & 0x3 206 if sub == 0b10: 207 # Monadic inline: 1 flit only (no data flit) 208 return 1 209 else: 210 # Frame control, PE-local write: flit 1 + flit 2 211 return 2 212 return 2 213 214 215def pack_token(token: Token) -> list[int]: 216 """Encode a token as a sequence of 16-bit flits. 217 218 Uses the exact hardware wire format. Flit 1 is the routing/header flit. 219 Used for T0 storage (EXEC reads these back) and future binary output. 220 221 Note: SMToken MemOps are currently limited to 3 bits (values 0-7) in the 222 wire format. Tier 2 MemOps (RD_DEC=8, CMP_SW=9, RAW_READ=10, SET_PAGE=11, 223 WRITE_IMM=12) would be silently truncated and are not yet supported. 224 """ 225 if isinstance(token, DyadToken): 226 dest = FrameDest( 227 target_pe=token.target, 228 offset=token.offset, 229 act_id=token.act_id, 230 port=token.port, 231 token_kind=TokenKind.DYADIC, 232 ) 233 flit1 = pack_flit1(dest) 234 flit2 = token.data & 0xFFFF 235 return [flit1, flit2] 236 elif isinstance(token, MonadToken): 237 kind = TokenKind.INLINE if token.inline else TokenKind.MONADIC 238 dest = FrameDest( 239 target_pe=token.target, 240 offset=token.offset, 241 act_id=token.act_id, 242 port=Port.L, 243 token_kind=kind, 244 ) 245 flit1 = pack_flit1(dest) 246 if token.inline: 247 return [flit1] # monadic inline: 1 flit only 248 flit2 = token.data & 0xFFFF 249 return [flit1, flit2] 250 elif isinstance(token, SMToken): 251 # SM: [1][SM_id:2][op:3][addr:10] (tier 1 layout) 252 # Tier 2 MemOps (values > 7) cannot fit in 3 bits and are not yet supported 253 if int(token.op) > 7: 254 raise ValueError( 255 f"SMToken MemOp {token.op} (value {int(token.op)}) exceeds 3-bit encoding limit. " 256 f"Tier 2 MemOps (RD_DEC=8, CMP_SW=9, RAW_READ=10, SET_PAGE=11, WRITE_IMM=12) " 257 f"are not yet supported in pack_token." 258 ) 259 flit1 = (1 << 15) | ((token.target & 0x3) << 13) | ((int(token.op) & 0x7) << 10) | (token.addr & 0x3FF) 260 flit2 = (token.data or 0) & 0xFFFF 261 return [flit1, flit2] 262 raise ValueError(f"Cannot pack token type: {type(token).__name__}") 263 264 265def unpack_token(flits: list[int]) -> Token: 266 """Decode a flit sequence into a Token object. 267 268 Flit 1 (flits[0]) is the header/routing flit. Decodes using hardware format. 269 """ 270 flit1 = flits[0] 271 272 if flit1 & 0x8000: 273 # SM token: [1][SM_id:2][op:3][addr:10] 274 sm_id = (flit1 >> 13) & 0x3 275 op = MemOp((flit1 >> 10) & 0x7) 276 addr = flit1 & 0x3FF 277 return SMToken( 278 target=sm_id, 279 addr=addr, 280 op=op, 281 flags=None, 282 data=flits[1] if len(flits) > 1 else 0, 283 ret=None, 284 ) 285 286 dest = unpack_flit1(flit1) 287 288 if dest.token_kind == TokenKind.DYADIC: 289 return DyadToken( 290 target=dest.target_pe, 291 offset=dest.offset, 292 act_id=dest.act_id, 293 data=flits[1] if len(flits) > 1 else 0, 294 port=dest.port, 295 ) 296 elif dest.token_kind == TokenKind.INLINE: 297 return MonadToken( 298 target=dest.target_pe, 299 offset=dest.offset, 300 act_id=dest.act_id, 301 data=0, 302 inline=True, 303 ) 304 else: 305 # MONADIC normal 306 return MonadToken( 307 target=dest.target_pe, 308 offset=dest.offset, 309 act_id=dest.act_id, 310 data=flits[1] if len(flits) > 1 else 0, 311 inline=False, 312 )