main.tpz + core/ + ui/ 모듈
// ===== main.tpz =====
// 회로 시뮬레이터 진입점 (S4: 시간 스크럽 + 프로브). 입력은 넷리스트 + 칩 정의(core.hierarchy 문법)에
// 더해 S4 줄을 가진다: tick(틱별 입력상태)·now(현재 틱)·probe(추적할 노드). 셸(JS)이 직렬화해 넘긴다.
// 파싱·평탄화·평가 후 현재 뷰를 ui.render로 그리고, 프로브가 있으면 ui.waveform 파형 패널을 덧붙인다.
// Circuit simulator entry (S4: time scrub + probe). Input is the netlist + chip defs (core.hierarchy grammar) plus
// S4 lines: tick (per-tick input-state), now (current tick), probe (a node to trace). The JS shell serializes them.
// After parse/flatten/evaluate it renders the current view, and appends a ui.waveform panel when there are probes.
//
// Key identifiers: 처리=handle, 이스케이프=escape, 기본회로=defaultCircuit, 지시어파싱=parseDirective, 상태파싱=parseState,
// 넷리스트=netlist, 이력=history, 프로브들=probes, 현재틱=currentTick, 파형패널=waveformPanel
import core.hierarchy { 분해, 평탄화, 경로뷰 }
import core.sim
import core.history
import core.netlist
import core.util
import core.fault { 고장파싱, 고장검증, 고장넷리스트, 고장노드들 }
import core.truthtable { 진리표 }
import ui.render { 그리기뷰 }
import ui.chrome { 빵부스러기 }
import ui.waveform { 그리기파형 }
import ui.inspector { 그리기검사 }
import app.serialize { 직렬화 }
import chips.arith { 산술라이브러리 }
import chips.alu { ALU라이브러리 }
import chips.seq { 순차라이브러리, 씨피유라이브러리 }
// 틱 상한(폭주 방지). JS가 그보다 짧게 유지하지만 코어도 막는다. / Hard tick cap (anti-runaway); the JS keeps it shorter.
let 틱상한 = 400
// 순차 정착 마이크로스텝 상한(되먹임 회로가 고정점에 못 가도 멈추게). / Sequential settle micro-step cap (bounds non-settling feedback).
let 정착상한 = 256
type 입력지시어 = "tick" | "now" | "probe" | "fault" | "truthtable" | "export" | "library" | "view" | "version" | "prev" | "netlist"
type 라이브러리지시어 = "arith" | "alu" | "seq" | "cpu" | "unknown"
// 기본 데모: 반가산기 칩(extin A,B; extout S,K; 내부 XOR+AND)을 부모가 입력 토글(a=1,b=0)로 구동해 LED로 읽는다.
// Default demo: a half-adder chip driven by parent input toggles into LEDs; double-click the chip to zoom inside.
let 기본회로 = "chipdef halfadder\nnode a INPUT 20 20\nnode b INPUT 20 100\nnode x XOR 150 30\nnode g AND 150 110\nnode s OUTPUT 280 45\nnode k OUTPUT 280 125\nwire a:out x:in0\nwire b:out x:in1\nwire a:out g:in0\nwire b:out g:in1\nwire x:out s:in0\nwire g:out k:in0\nextin A a\nextin B b\nextout S s\nextout K k\nendchip\nnode pa INPUT 30 60 1\nnode pb INPUT 30 170 0\nchip ha halfadder 190 80\nnode ps OUTPUT 380 95\nnode pk OUTPUT 380 155\nwire pa:out ha:A\nwire pb:out ha:B\nwire ha:S ps:in0\nwire ha:K pk:in0"
// 원시 줄 머리를 입력 지시어로 좁힌다. 모르는 머리는 넷리스트 줄로 유지한다.
// Narrow a raw line head to an input directive. Unknown heads stay netlist lines.
function 지시어파싱(머리: string) -> 입력지시어 {
match 머리 {
case "tick" => "tick"
case "now" => "now"
case "probe" => "probe"
case "fault" => "fault"
case "truthtable" => "truthtable"
case "export" => "export"
case "library" => "library"
case "view" => "view"
case "version" => "version"
case "prev" => "prev"
case _ => "netlist"
}
}
// library 토큰은 닫힌 명령으로 좁히되, 모르는 이름은 기존처럼 무시한다.
// Narrow library tokens to the closed command set; unknown names are ignored as before.
function 라이브러리파싱(이름: string) -> 라이브러리지시어 {
match 이름 {
case "arith" => "arith"
case "alu" => "alu"
case "seq" => "seq"
case "cpu" => "cpu"
case _ => "unknown"
}
}
// HTML 이스케이프. 오류 메시지는 사용자 토큰을 담을 수 있으므로 출력 전에 반드시 이스케이프한다.
// HTML escape. Error messages may carry user tokens, so always escape before output.
function 이스케이프(원문: string) -> string {
let mut 결과 = ""
for 글 in 원문.scalars() {
if 글 == "&" { 결과 = 결과 + "&" }
else if 글 == "<" { 결과 = 결과 + "<" }
else if 글 == ">" { 결과 = 결과 + ">" }
else if 글 == "\"" { 결과 = 결과 + """ }
else { 결과 = 결과 + 글 }
}
결과
}
// 틱 입력상태 "pa=1,pb=0" → 입력값 배열. / Parse a tick input-state "pa=1,pb=0" -> input-value list.
function 상태파싱(문자열: string) -> Array<history.입력값> {
let mut 결과: Array<history.입력값> = []
for 쌍 in 문자열.split(",") {
let 부분 = 쌍.split("=")
if 부분.length == 2 {
결과.push({ 아이디: 부분[0], 값: if 부분[1] == "1" { 1 } else { 0 } })
}
}
결과
}
// 틱 상태에서 한 입력의 값(없으면 기본값). / The value of one input in a tick state (default if absent).
function 틱값(상태: Array<history.입력값>, 아이디: string, 기본: int) -> int {
let mut 값 = 기본
for 입 in 상태 { if 입.아이디 == 아이디 { 값 = 입.값 } }
값
}
// prev 디렉티브의 "아이디=값,..." 벡터를 노드신호 배열로 파싱한다(순차 회로의 이전 정착 상태).
// Parse a prev directive's "id=val,..." vector into a node-signal array (a sequential circuit's prior settled state).
function 상태벡터파싱(문자열: string) -> Array<sim.노드신호> {
let mut 결과: Array<sim.노드신호> = []
for 쌍 in 문자열.split(",") {
let 부분 = 쌍.split("=")
if 부분.length == 2 {
결과.push({ 아이디: 부분[0], 신호: 부분[1] == "1", 프레임: 0 })
}
}
결과
}
// 정착 상태를 "아이디=값,..." 한 줄로 직렬화한다(셸이 <!--state--> 뒤에서 받아 다음 틱의 prev로 되돌려준다).
// Serialize the settled state to a single "id=val,..." line (the shell reads it after <!--state--> and feeds it back as next tick's prev).
function 상태직렬화(신호들: Array<sim.노드신호>) -> string {
let mut 결과 = ""
let mut 아이 = 0
while 아이 < 신호들.length {
if 아이 > 0 { 결과 = 결과 + "," }
결과 = 결과 + 신호들[아이].아이디 + "=" + (if 신호들[아이].신호 { "1" } else { "0" })
아이 = 아이 + 1
}
결과
}
function 처리(입력: string) -> string {
let 원본 = if 입력.trim() == "" { 기본회로 } else { 입력 }
// 넷리스트 줄과 S4(tick/now/probe) 줄을 분리한다. / Separate netlist lines from S4 (tick/now/probe) lines.
let mut 넷리스트 = ""
let mut 이력: Array<Array<history.입력값>> = []
let mut 프로브들: Array<string> = []
let mut 현재틱 = -1
let mut 고장줄들: Array<Array<string>> = []
let mut 진리표요청 = false
let mut 내보내기요청 = false
let mut 산술요청 = false
let mut 알루요청 = false
let mut 순차요청 = false
let mut 씨피유요청 = false
let mut 뷰경로: Array<string> = []
let mut 이전상태문자열 = ""
let mut 이전있음 = false
for 줄 in 원본.split("\n") {
let 토큰 = util.빈칸제거(줄.trim().split(" "))
let 머리 = if 토큰.length > 0 { 토큰[0] } else { "" }
let 지시어: 입력지시어 = 지시어파싱(머리)
match 지시어 {
case "tick" => {
if 토큰.length >= 3 && 이력.length < 틱상한 { 이력.push(상태파싱(토큰[2])) }
}
case "now" => {
if 토큰.length >= 2 { 현재틱 = toInt(토큰[1]) ?? -1 }
}
case "probe" => {
if 토큰.length >= 2 { 프로브들.push(토큰[1]) }
}
case "fault" => {
고장줄들.push(토큰)
}
case "truthtable" => {
진리표요청 = true
}
case "export" => {
내보내기요청 = true
}
case "library" => {
// 라이브러리 칩 정의를 앞에 붙인다(arith: 반/전가산기·4비트 가산기 / alu: 거기에 mux2·4비트 ALU 추가).
// Prepend a library's chip defs (arith: half/full adder + adder4; alu: those plus mux2 + the 4-bit ALU).
let 라이브러리: 라이브러리지시어 = if 토큰.length >= 2 { 라이브러리파싱(토큰[1]) } else { "unknown" }
match 라이브러리 {
case "arith" => { 산술요청 = true }
case "alu" => { 알루요청 = true }
case "seq" => { 순차요청 = true }
case "cpu" => { 씨피유요청 = true }
case "unknown" => {}
}
}
case "view" => {
// 뷰 경로 = 인스턴스 체인(view i1 i2 ... ik). 최상위에서 ik까지 깊은 중첩 뷰로 내려간다.
// View path = an instance chain; descends from the top into nested chips for the deep semantic zoom.
let mut 자 = 1
while 자 < 토큰.length { 뷰경로.push(토큰[자]); 자 = 자 + 1 }
}
case "version" => {
// 정규 내보내기 헤더는 무시한다(라운드트립 안전). / Ignore the canonical export header (round-trip safe).
}
case "prev" => {
// 이전 정착 상태(전체 평탄 노드 벡터). 순차 회로의 되먹임 상태를 셸이 틱마다 넘긴다.
// The prior settled state (full flat node vector); the shell threads a sequential circuit's feedback state each tick.
if 토큰.length >= 3 { 이전상태문자열 = 토큰[2]; 이전있음 = true }
}
case "netlist" => {
넷리스트 = 넷리스트 + 줄 + "\n"
}
}
}
// 요청한 라이브러리를 앞에 붙인다(alu는 arith를 포함하므로 배타, seq는 가산). / Prepend requested libraries (alu includes arith so they are exclusive; seq is additive).
let mut 라이브 = ""
if 씨피유요청 {
// cpu = alu(mux2·alu4) + seq(reg4le·ram4 등) + CPU 전용 칩(데이터패스 등), 정의 순서대로.
// cpu = alu + seq + the CPU-only chips, in dependency order (one self-contained directive).
라이브 = ALU라이브러리() + 순차라이브러리() + 씨피유라이브러리()
} else {
if 알루요청 { 라이브 = 라이브 + ALU라이브러리() } else if 산술요청 { 라이브 = 라이브 + 산술라이브러리() }
if 순차요청 { 라이브 = 라이브 + 순차라이브러리() }
}
let 넷리스트전체 = 라이브 + 넷리스트
let 분해0 = 분해(넷리스트전체)
if 분해0.오류 != "" {
return "<div class=\"err\">넷리스트 오류: " + 이스케이프(분해0.오류) + "</div>"
}
// now <n>이 설정되고 이력이 있으면 그 틱의 입력상태를 본체 INPUT 값에 적용해 회로(평탄·시뮬·렌더)도 그 틱 상태로 만든다.
// When now <n> is set with history, apply that tick's input-state to the body INPUT values so the CIRCUIT renders at tick n.
let mut 적용노드들: Array<netlist.노드> = []
if 현재틱 >= 0 && 현재틱 < 이력.length {
let 상태 = 이력[현재틱]
for 엔 in 분해0.주노드들 {
match 엔.종류 {
case "INPUT" => {
적용노드들.push({ 아이디: 엔.아이디, 종류: 엔.종류, 엑스: 엔.엑스, 와이: 엔.와이, 값: 틱값(상태, 엔.아이디, 엔.값) })
}
case "OUTPUT" => { 적용노드들.push(엔) }
case "NAND" => { 적용노드들.push(엔) }
case "AND" => { 적용노드들.push(엔) }
case "OR" => { 적용노드들.push(엔) }
case "NOT" => { 적용노드들.push(엔) }
case "XOR" => { 적용노드들.push(엔) }
case "NOR" => { 적용노드들.push(엔) }
case "XNOR" => { 적용노드들.push(엔) }
}
}
} else {
for 엔 in 분해0.주노드들 { 적용노드들.push(엔) }
}
let 분해결과 = { 정의들: 분해0.정의들, 주노드들: 적용노드들, 주전선들: 분해0.주전선들, 인스턴스들: 분해0.인스턴스들, 뷰: 분해0.뷰, 오류: 분해0.오류 }
let 평탄 = 평탄화(분해결과)
// 고장(stuck-at) 파싱·검증 후, 결함 노드를 고정값 INPUT으로 바꾼 회로로 시뮬한다(라이브 회로가 망가짐).
// Parse/validate faults, then sim a circuit where faulted nodes are stuck-value INPUTs (the live circuit breaks).
let 고장파싱결과 = 고장파싱(고장줄들)
if 고장파싱결과.오류 != "" {
return "<div class=\"err\">" + 이스케이프(고장파싱결과.오류) + "</div>"
}
let 고장들 = 고장파싱결과.고장들
let 고장검증오류 = 고장검증(고장들, 평탄.노드들)
if 고장검증오류 != "" {
return "<div class=\"err\">" + 이스케이프(고장검증오류) + "</div>"
}
let 고장평탄 = 고장넷리스트(평탄, 고장들)
// 순차 모드(prev 디렉티브가 있거나 회로에 되먹임이 있음)면 unit-delay 정착, 아니면 기존 조합 평가(바이트 동일).
// Sequential mode (a prev directive, or feedback in the circuit) -> unit-delay settle; else the existing combinational evaluation (byte-identical).
let 조합시뮬 = sim.평가회로(고장평탄)
let 순차 = 이전있음 || 조합시뮬.오류 != ""
let 이전상태 = 상태벡터파싱(이전상태문자열)
let 정착 = if 순차 { sim.정착평가회로(고장평탄, 이전상태, 정착상한) } else { { 시뮬: 조합시뮬, 상태: "settled", 스텝: 조합시뮬.프레임수 } }
let 시뮬 = 정착.시뮬
if 시뮬.오류 != "" {
return "<div class=\"err\">" + 이스케이프(시뮬.오류) + "</div>"
}
let 고장아이디들 = 고장노드들(고장들)
// 뷰 경로가 비면 최상위, 아니면 경로뷰로 그 깊이의 본체(하위 칩 상자 포함)를 그린다. 깊은 줌은 단계마다 독립 평가한다.
// Empty path = top view; otherwise 경로뷰 renders that depth's body (with sub-chip boxes), evaluated per level.
let mut 본문 = ""
let mut 이름들: Array<string> = []
let mut 표시노드수 = 분해결과.주노드들.length
let mut 표시칩수 = 분해결과.인스턴스들.length
if 뷰경로.length == 0 {
본문 = 그리기뷰(분해결과, 시뮬, 고장아이디들)
} else {
let 경로결과 = 경로뷰(분해결과, 시뮬, 뷰경로, 고장들)
if 경로결과.오류 != "" {
return "<div class=\"err\">" + 이스케이프(경로결과.오류) + "</div>"
}
본문 = 그리기뷰(경로결과.뷰분해, 경로결과.뷰시뮬, 고장노드들(경로결과.뷰고장들))
이름들 = 경로결과.이름들
표시노드수 = 경로결과.뷰분해.주노드들.length
표시칩수 = 경로결과.뷰분해.인스턴스들.length
}
let 머리줄 = 빵부스러기(이름들)
let 노드수 = 표시노드수
let 칩수 = 표시칩수
let 메타 = "<div class=\"meta\">노드 {노드수}개, 칩 {칩수}개. 입력을 토글하거나 칩을 더블클릭하고, 전선을 눌러 파형을 봅니다.</div>"
// 되먹임(순차) 회로면 조합 전용 패널(파형·진리표)이 거짓을 보이므로 정직하게 막고 안내한다. 조합 회로는 그대로다.
// Feedback (sequential) circuits make the combinational-only panels (waveform, truth table) lie, so suppress them honestly. Combinational circuits are byte-identical.
let 되먹임 = sim.평가회로(평탄).오류 != ""
// 프로브가 있으면 틱마다 다시 평가해 파형 패널을 그린다(현재 틱 기본값은 마지막 틱). 순차 회로는 조합 파형이 거짓이라 안내로 대체한다.
// With probes, re-evaluate each tick and draw the waveform (current tick defaults to the last). A sequential circuit's combinational waveform would lie, so show a note instead.
let 파형패널 = if 프로브들.length > 0 && 이력.length > 0 {
if 되먹임 {
"<div class=\"warn\">파형은 조합 회로 전용입니다 — 순차 회로의 신호는 시간축을 스크럽해 보세요.</div>"
} else {
let 틱 = if 현재틱 >= 0 && 현재틱 < 이력.length { 현재틱 } else { 이력.length - 1 }
let 이력결과 = history.파형계산(고장평탄, 이력, 프로브들)
그리기파형(이력결과.파형들, 틱)
}
} else { "" }
// 파형 패널은 카메라(팬·줌) 밖 고정 패널로 가야 하므로 셸이 가를 수 있게 주석 구분자를 둔다.
// The waveform belongs in a fixed panel outside the pan/zoom camera, so emit a comment separator the shell splits on.
let 파형구분 = if 파형패널 != "" { "<!--파형-->" } else { "" }
// 진리표 요청 시 검사 패널을 덧붙인다. 진리표는 고장 없는 회로로 계산한 정답이라 고장난 라이브 회로와 대비된다.
// With a truthtable request, append the inspector. The table is the unfaulted correct result, contrasting the faulted live circuit.
let 검사패널 = if 진리표요청 {
if 되먹임 {
"<div class=\"warn\">진리표는 조합 회로 전용입니다 — 순차 회로의 출력은 현재 상태에 따라 달라집니다.</div>"
} else {
그리기검사(진리표(평탄), 고장들.length > 0)
}
} else { "" }
let 검사구분 = if 검사패널 != "" { "<!--검사-->" } else { "" }
// 내보내기 요청 시 정규 직렬화를 구분자 뒤에 붙인다(셸이 .circuit 파일로 추출). 기본 회로(분해0)를 쓴다.
// On an export request, append the canonical serialization after a sentinel (the shell extracts it to a .circuit file).
let 내보내기 = if 내보내기요청 { "<!--내보내기-->" + 직렬화(분해0) } else { "" }
// 순차 정착이 발진/미정착이면 경고 배너를 보인다(오류로 중단하지 않는다). / Warn (not error) when a sequential settle oscillates or fails to settle.
let 배너 = match 정착.상태 {
case "oscillating" => "<div class=\"warn\">회로가 발진합니다 — 정착하지 않는 되먹임입니다.</div>"
case "unsettled" => "<div class=\"warn\">회로가 정착하지 않았습니다 — 스텝 한도를 넘었습니다.</div>"
case "settled" => ""
}
// 순차 모드면 정착 벡터와 메타를 기계용 주석으로 내보낸다(셸이 다음 틱 prev로 되먹인다). 내보내기 앞에 둔다.
// In sequential mode, emit the settled vector + meta as machine comments (the shell feeds it back as next tick's prev). Placed before the export.
let 상태문자 = 정착.상태
let 스텝수 = 정착.스텝
let 상태출력 = if 순차 { "<!--state-->" + 상태직렬화(시뮬.신호들) + "<!--simmeta-->status={상태문자},steps={스텝수}" } else { "" }
머리줄 + 메타 + 배너 + 본문 + 파형구분 + 파형패널 + 검사구분 + 검사패널 + 상태출력 + 내보내기
}
print(처리(input()))
// ===== core/gate.tpz =====
// 정직한 게이트 코어. 원시 게이트는 NAND(진리표)이고, 나머지는 모두 NAND를 조립해 파생한다.
// 신호 경로에 산술(+ - * /)이 전혀 없다. 게이트 출력은 NAND 진리표와 그 합성으로만 나온다.
// Honest gate core. The primitive is NAND (a truth table); every other gate is composed from NAND.
// No arithmetic on the signal path: gate outputs come only from the NAND truth table and compositions.
//
// 하우스룰(core): export는 타입/함수/불변 최상위 바인딩만. 최상위(모듈 레벨) 가변 상태·print·input·host 금지.
// 함수 안의 지역 let mut는 순수·결정론을 깨지 않으므로 허용한다.
// House rule (core): export only types, functions, and immutable top-level bindings; no module-level mutable
// state, no print, no input, no host/clock. A local `let mut` inside a function is fine (it stays pure).
//
// Key identifiers: 난드=nand, 낫=not, 앤드=and, 오어=or, 노어=nor, 엑스오어=xor, 엑스노어=xnor,
// 평가=evaluate, 종류=kind, 입력수=arity, 종류목록=kindList, 가=a, 나=b, 가운데=middle
// 닫힌 게이트 종류. 새 게이트를 추가하면 입력수·평가 match가 exhaustiveness로 함께 갱신된다.
// Closed gate kind. Adding a gate forces arity/evaluation matches to update together.
export type 게이트종류 = "NAND" | "AND" | "OR" | "NOT" | "XOR" | "NOR" | "XNOR"
// NAND 진리표: 두 입력이 모두 참일 때만 거짓. 네 경우를 if로 직접 나열한다(산술 없음).
// NAND truth table: false only when both inputs are true. The cases are enumerated with if (no arithmetic).
export function 난드(가: bool, 나: bool) -> bool {
if 가 {
if 나 { false } else { true }
} else {
true
}
}
// NOT = NAND(a, a). 이하 모든 게이트는 난드만으로 조립한다.
// Every derived gate below is assembled only from 난드.
export function 낫(가: bool) -> bool { 난드(가, 가) }
// AND = NOT(NAND(a, b)).
export function 앤드(가: bool, 나: bool) -> bool { 낫(난드(가, 나)) }
// OR = NAND(NOT a, NOT b).
export function 오어(가: bool, 나: bool) -> bool { 난드(낫(가), 낫(나)) }
// NOR = NOT(OR(a, b)).
export function 노어(가: bool, 나: bool) -> bool { 낫(오어(가, 나)) }
// XOR = NAND(NAND(a, NAND(a,b)), NAND(b, NAND(a,b))). 네 게이트짜리 표준 구성.
// The standard four-NAND XOR.
export function 엑스오어(가: bool, 나: bool) -> bool {
let 가운데 = 난드(가, 나)
난드(난드(가, 가운데), 난드(나, 가운데))
}
// XNOR = NOT(XOR(a, b)).
export function 엑스노어(가: bool, 나: bool) -> bool { 낫(엑스오어(가, 나)) }
// 지원 게이트 종류. / The supported gate kinds.
export let 종류목록: Array<게이트종류> = ["NAND", "AND", "OR", "NOT", "XOR", "NOR", "XNOR"]
// host/netlist 경계의 원시 문자열을 닫힌 게이트 종류로 좁힌다. 손상 값은 NAND로 안전 복귀한다.
// Narrow a raw host/netlist string into the closed gate kind. Corrupt values fall back to NAND.
export function 게이트종류파싱(토큰: string) -> 게이트종류 {
match 토큰 {
case "NAND" => "NAND"
case "AND" => "AND"
case "OR" => "OR"
case "NOT" => "NOT"
case "XOR" => "XOR"
case "NOR" => "NOR"
case "XNOR" => "XNOR"
case _ => "NAND"
}
}
// 원시 문자열이 지원 게이트 종류인가. / Is a raw string a supported gate kind?
export function 게이트종류유효(토큰: string) -> bool {
match 토큰 {
case "NAND" => true
case "AND" => true
case "OR" => true
case "NOT" => true
case "XOR" => true
case "NOR" => true
case "XNOR" => true
case _ => false
}
}
// 입력 수: NOT은 1, 나머지는 2. 표시용 메타데이터이며 신호가 아니다.
// Arity: NOT takes 1, the rest take 2. Display metadata, not a signal.
export function 입력수(종류: 게이트종류) -> int {
match 종류 {
case "NOT" => 1
case "NAND" => 2
case "AND" => 2
case "OR" => 2
case "XOR" => 2
case "NOR" => 2
case "XNOR" => 2
}
}
// 종류로 평가. 출력은 위 게이트(= 난드 합성)로만 계산된다.
// Evaluate by kind. The output is computed only by the gates above (NAND compositions).
export function 평가(종류: 게이트종류, 가: bool, 나: bool) -> bool {
match 종류 {
case "NAND" => 난드(가, 나)
case "AND" => 앤드(가, 나)
case "OR" => 오어(가, 나)
case "NOT" => 낫(가)
case "XOR" => 엑스오어(가, 나)
case "NOR" => 노어(가, 나)
case "XNOR" => 엑스노어(가, 나)
}
}
// ===== core/util.tpz =====
// 회로 앱 공통 순수 유틸리티. 파서들이 공유하는 작은 문자열 검증만 둔다.
// Shared pure utilities for the circuit app: only small string validators used by parsers.
//
// 하우스룰(core): export는 타입/함수/불변 최상위만. 최상위 가변·print·input·host 금지.
// House rule (core): export only types/functions/immutable bindings; no module-level mutable/print/input/host.
// 빈 토큰을 걸러낸다(다중 공백 대비). / Drop empty tokens (guards against multiple spaces).
export function 빈칸제거(토큰들: Array<string>) -> Array<string> {
let mut 결과: Array<string> = []
for 토 in 토큰들 {
if 토 != "" { 결과.push(토) }
}
결과
}
// 정수 문자열인가(음수 허용). / Is the token an integer (optional leading minus)?
export function 정수문자열(글자: string) -> bool {
let 칸 = 글자.scalars()
if 칸.length == 0 { return false }
let mut 인덱스 = 0
let mut 좋음 = true
for 글 in 칸 {
let 코드 = 글.codePointAt(0) ?? 0
let 마이너스 = 글 == "-" && 인덱스 == 0 && 칸.length > 1
if !(마이너스 || (코드 >= 48 && 코드 <= 57)) { 좋음 = false }
인덱스 = 인덱스 + 1
}
좋음
}
// 영숫자와 밑줄만 허용하는 안전 아이디. SVG/data 속성과 계층 네임스페이스에 그대로 들어간다.
// Safe alphanumeric/underscore ids, used directly in SVG/data attributes and hierarchy namespaces.
export function 안전아이디(아이디: string) -> bool {
let 칸 = 아이디.scalars()
if 칸.length == 0 || 칸.length > 24 { return false }
if 아이디.contains("__") { return false }
let mut 좋음 = true
for 글 in 칸 {
let 코드 = 글.codePointAt(0) ?? 0
let 숫자 = 코드 >= 48 && 코드 <= 57
let 대문자 = 코드 >= 65 && 코드 <= 90
let 소문자 = 코드 >= 97 && 코드 <= 122
let 밑줄 = 글 == "_"
if !(숫자 || 대문자 || 소문자 || 밑줄) { 좋음 = false }
}
좋음
}
// ===== core/netlist.tpz =====
// 회로 그래프: 노드(게이트/입출력) + 전선(핀 사이 연결) 데이터 모델과 넷리스트 파서.
// core는 순수하다: Host·UI 없음, 신호 산술 없음(게이트 진리는 core.gate). 좌표는 표시 메타데이터일 뿐이다.
// Circuit graph: nodes (gates / IO) + wires (pin to pin) data model and a netlist parser.
// Pure core: no Host/UI, no signal arithmetic (gate truth lives in core.gate). Coords are display metadata.
//
// 하우스룰(core): export는 타입/함수/불변 최상위 바인딩만. 최상위(모듈 레벨) 가변 상태·print·input·host 금지.
// 함수 안의 지역 let mut는 순수·결정론을 깨지 않으므로 허용한다.
// House rule (core): export only types, functions, and immutable top-level bindings; no module-level mutable
// state, no print, no input, no host/clock. A local `let mut` inside a function is fine (it stays pure).
//
// 넷리스트 문법(한 줄에 하나):
// node <아이디> <종류> <엑스> <와이> [<값>] 종류: INPUT OUTPUT NAND AND OR NOT XOR NOR XNOR; 값(0/1)은 INPUT만
// wire <시작아이디>:<핀> <끝아이디>:<핀> 시작핀=출력(out), 끝핀=입력(in0/in1)
//
// Key identifiers: 노드=node, 전선=wire, 회로=circuit, 파싱결과=parseResult, 아이디=id, 종류=kind,
// 엑스/와이=x/y, 시작/끝=from/to, 핀=pin, 파싱=parse, 오류=error, 숫자인가=isInt, 아이디유효=idOk,
// 노드종류유효=kindOk, 노드종류파싱=parseKind, 출력핀유효=outPinOk, 입력핀유효=inPinOk, 빈칸제거=dropBlanks, 핀분해=splitPin
import core.gate
import core.util
// 닫힌 노드 종류. 아이디·칩 이름·핀 이름은 열린 문자열이지만, 노드 kind는 회로 문법의 닫힌 태그다.
// Closed node kind. Ids/chip names/pin names stay open strings, but node kind is a closed grammar tag.
export type 노드종류 = "INPUT" | "OUTPUT" | "NAND" | "AND" | "OR" | "NOT" | "XOR" | "NOR" | "XNOR"
export type 노드 = { 아이디: string, 종류: 노드종류, 엑스: int, 와이: int, 값: int }
export type 전선 = { 시작노드: string, 시작핀: string, 끝노드: string, 끝핀: string }
export type 회로 = { 노드들: Array<노드>, 전선들: Array<전선> }
export type 파싱결과 = { 회로: 회로, 오류: string }
type 종류조회 = { 있음: bool, 종류: 노드종류 }
type 핀조각 = { 좋음: bool, 노드: string, 핀: string }
// 노드 종류가 유효한가(INPUT/OUTPUT 또는 게이트). / Is the node kind supported?
function 노드종류유효(종류: string) -> bool {
match 종류 {
case "INPUT" => true
case "OUTPUT" => true
case _ => gate.게이트종류유효(종류)
}
}
// 원시 토큰을 닫힌 노드 종류로 좁힌다. 호출 전 노드종류유효로 검증하므로 fallback은 방어선이다.
// Narrow a raw token into the closed node kind. The caller validates first; fallback is defensive.
export function 노드종류파싱(종류: string) -> 노드종류 {
match 종류 {
case "INPUT" => "INPUT"
case "OUTPUT" => "OUTPUT"
case _ => gate.게이트종류파싱(종류)
}
}
// 표시·평가용 입력 핀 수. INPUT은 입력 핀이 없고 OUTPUT은 in0 하나를 받는다.
// Arity for display/evaluation pins. INPUT has no input pins; OUTPUT takes one in0.
export function 입력수(종류: 노드종류) -> int {
match 종류 {
case "INPUT" => 0
case "OUTPUT" => 1
case "NAND" => gate.입력수("NAND")
case "AND" => gate.입력수("AND")
case "OR" => gate.입력수("OR")
case "NOT" => gate.입력수("NOT")
case "XOR" => gate.입력수("XOR")
case "NOR" => gate.입력수("NOR")
case "XNOR" => gate.입력수("XNOR")
}
}
// 노드 종류로 출력 신호를 계산한다. INPUT/OUTPUT은 게이트가 아니므로 들어온 값을 그대로 통과한다.
// Compute a node output by kind. INPUT/OUTPUT are not gates, so they pass the incoming value through.
export function 출력계산(종류: 노드종류, 가: bool, 나: bool) -> bool {
match 종류 {
case "INPUT" => 가
case "OUTPUT" => 가
case "NAND" => gate.평가("NAND", 가, 나)
case "AND" => gate.평가("AND", 가, 나)
case "OR" => gate.평가("OR", 가, 나)
case "NOT" => gate.평가("NOT", 가, 나)
case "XOR" => gate.평가("XOR", 가, 나)
case "NOR" => gate.평가("NOR", 가, 나)
case "XNOR" => gate.평가("XNOR", 가, 나)
}
}
// 노드 kind 분류 헬퍼. 닫힌 union을 한곳에서 exhaustive match로 분류해 하위 모듈의 if-chain을 줄인다.
// Node-kind classifiers. Keep the closed-union exhaustive matches in one place.
export function 입력노드인가(종류: 노드종류) -> bool {
match 종류 {
case "INPUT" => true
case "OUTPUT" => false
case "NAND" => false
case "AND" => false
case "OR" => false
case "NOT" => false
case "XOR" => false
case "NOR" => false
case "XNOR" => false
}
}
export function 출력노드인가(종류: 노드종류) -> bool {
match 종류 {
case "OUTPUT" => true
case "INPUT" => false
case "NAND" => false
case "AND" => false
case "OR" => false
case "NOT" => false
case "XOR" => false
case "NOR" => false
case "XNOR" => false
}
}
// 시작핀(출력)이 유효한가. INPUT/게이트는 out, OUTPUT은 출력 핀이 없다.
// Is the from-pin a valid output pin? INPUT and gates expose out; OUTPUT has none.
function 출력핀유효(종류: string, 핀: string) -> bool {
let 닫힌 = 노드종류파싱(종류)
match 닫힌 {
case "OUTPUT" => false
case "INPUT" => 핀 == "out"
case "NAND" => 핀 == "out"
case "AND" => 핀 == "out"
case "OR" => 핀 == "out"
case "NOT" => 핀 == "out"
case "XOR" => 핀 == "out"
case "NOR" => 핀 == "out"
case "XNOR" => 핀 == "out"
}
}
// 끝핀(입력)이 유효한가. OUTPUT/게이트는 in0(2입력 게이트는 in1도), INPUT은 입력 핀이 없다.
// Is the to-pin a valid input pin? OUTPUT and gates expose in0 (and in1 for 2-input gates); INPUT has none.
function 입력핀유효(종류: string, 핀: string) -> bool {
let 닫힌 = 노드종류파싱(종류)
match 닫힌 {
case "INPUT" => false
case "OUTPUT" => 핀 == "in0"
case "NOT" => 핀 == "in0"
case "NAND" => 핀 == "in0" || 핀 == "in1"
case "AND" => 핀 == "in0" || 핀 == "in1"
case "OR" => 핀 == "in0" || 핀 == "in1"
case "XOR" => 핀 == "in0" || 핀 == "in1"
case "NOR" => 핀 == "in0" || 핀 == "in1"
case "XNOR" => 핀 == "in0" || 핀 == "in1"
}
}
// 아이디로 종류 조회. / Look up a node's kind by id.
function 노드종류로(노드들: Array<노드>, 아이디: string) -> 종류조회 {
let mut 결과: 종류조회 = { 있음: false, 종류: "INPUT" }
for 엔 in 노드들 {
if 엔.아이디 == 아이디 { 결과 = { 있음: true, 종류: 엔.종류 } }
}
결과
}
function 노드있음(노드들: Array<노드>, 아이디: string) -> bool {
노드종류로(노드들, 아이디).있음
}
// "노드:핀"을 분해한다. / Split "node:pin".
function 핀분해(토큰: string) -> 핀조각 {
let 부분 = 토큰.split(":")
if 부분.length == 2 && 부분[0] != "" && 부분[1] != "" {
{ 좋음: true, 노드: 부분[0], 핀: 부분[1] }
} else {
{ 좋음: false, 노드: "", 핀: "" }
}
}
// 전선 후검증: 양 끝 노드 존재 + 핀 유효 + 입력 핀 중복 구동 금지. 오류 문자열(없으면 "")을 돌려준다.
// Post-validate wires: both endpoints exist, pins are valid, no input pin driven twice. Returns "" when ok.
function 전선검증(노드들: Array<노드>, 전선들: Array<전선>) -> string {
let mut 오류 = ""
let mut 인덱스 = 0
while 인덱스 < 전선들.length {
if 오류 == "" {
let 더블유 = 전선들[인덱스]
let 시작 = 노드종류로(노드들, 더블유.시작노드)
let 끝 = 노드종류로(노드들, 더블유.끝노드)
if !시작.있음 {
오류 = "전선이 없는 노드를 가리킵니다: " + 더블유.시작노드
} else if !끝.있음 {
오류 = "전선이 없는 노드를 가리킵니다: " + 더블유.끝노드
} else if !출력핀유효(시작.종류, 더블유.시작핀) {
오류 = "전선 시작은 출력 핀이어야 합니다: " + 더블유.시작노드 + ":" + 더블유.시작핀
} else if !입력핀유효(끝.종류, 더블유.끝핀) {
오류 = "전선 끝은 입력 핀이어야 합니다: " + 더블유.끝노드 + ":" + 더블유.끝핀
}
}
인덱스 = 인덱스 + 1
}
if 오류 == "" {
let mut 아이 = 0
while 아이 < 전선들.length {
if 오류 == "" {
let mut 카운트 = 0
let mut 제이 = 0
while 제이 < 전선들.length {
if 전선들[제이].끝노드 == 전선들[아이].끝노드 && 전선들[제이].끝핀 == 전선들[아이].끝핀 {
카운트 = 카운트 + 1
}
제이 = 제이 + 1
}
if 카운트 > 1 {
오류 = "한 입력 핀에 전선이 둘 이상입니다: " + 전선들[아이].끝노드 + ":" + 전선들[아이].끝핀
}
}
아이 = 아이 + 1
}
}
오류
}
// 넷리스트 문자열을 파싱한다. 첫 오류에서 멈춰 회로와 오류 문자열을 함께 돌려준다.
// Parse a netlist string. Stops at the first error and returns the circuit plus an error string.
export function 파싱(원문: string) -> 파싱결과 {
let mut 노드들: Array<노드> = []
let mut 전선들: Array<전선> = []
let mut 오류 = ""
let 줄들 = 원문.split("\n")
let mut 줄번호 = 0
while 줄번호 < 줄들.length {
if 오류 == "" {
let 토큰들 = util.빈칸제거(줄들[줄번호].trim().split(" "))
if 토큰들.length > 0 {
let 머리 = 토큰들[0]
if 머리 == "node" {
if 토큰들.length != 5 && 토큰들.length != 6 {
오류 = "node 줄 형식은 node 아이디 종류 엑스 와이 [값] 입니다"
} else if !util.안전아이디(토큰들[1]) {
오류 = "유효하지 않은 노드 아이디입니다: " + 토큰들[1]
} else if !노드종류유효(토큰들[2]) {
오류 = "알 수 없는 게이트 종류입니다: " + 토큰들[2]
} else if !util.정수문자열(토큰들[3]) || !util.정수문자열(토큰들[4]) {
오류 = "좌표는 정수여야 합니다"
} else if 토큰들.length == 6 && 토큰들[2] != "INPUT" {
오류 = "값은 INPUT 노드에만 붙습니다"
} else if 토큰들.length == 6 && 토큰들[5] != "0" && 토큰들[5] != "1" {
오류 = "입력 값은 0 또는 1이어야 합니다"
} else if 노드있음(노드들, 토큰들[1]) {
오류 = "중복된 노드 아이디입니다: " + 토큰들[1]
} else {
let 값 = if 토큰들.length == 6 { if 토큰들[5] == "1" { 1 } else { 0 } } else { 0 }
let 종류값: 노드종류 = 노드종류파싱(토큰들[2])
노드들.push({ 아이디: 토큰들[1], 종류: 종류값, 엑스: toInt(토큰들[3]) ?? 0, 와이: toInt(토큰들[4]) ?? 0, 값: 값 })
}
} else if 머리 == "wire" {
if 토큰들.length != 3 {
오류 = "wire 줄 형식은 wire 시작:핀 끝:핀 입니다"
} else {
let 시작 = 핀분해(토큰들[1])
let 끝 = 핀분해(토큰들[2])
if !시작.좋음 || !끝.좋음 {
오류 = "전선 끝점은 노드:핀 형식입니다"
} else {
전선들.push({ 시작노드: 시작.노드, 시작핀: 시작.핀, 끝노드: 끝.노드, 끝핀: 끝.핀 })
}
}
} else {
오류 = "알 수 없는 줄입니다: " + 머리
}
}
}
줄번호 = 줄번호 + 1
}
if 오류 == "" {
오류 = 전선검증(노드들, 전선들)
}
{ 회로: { 노드들: 노드들, 전선들: 전선들 }, 오류: 오류 }
}
// ===== core/sim.tpz =====
// 조합 회로 전파 엔진. 위상 순서(Kahn)로 게이트를 풀고, 게이트 출력은 core.gate 진리표로만 계산한다.
// 노드마다 출력 신호(0/1)와 정착 프레임(위상 층)을 낸다. 순환(되먹임)은 명확한 오류로 막는다.
// core는 순수: Host·UI 없음. 신호 진리는 core.gate에 있고, 여기 산술은 프레임·인덱스 살림뿐(신호 산술 아님).
// Combinational propagation engine. Topological (Kahn) evaluation; gate outputs come ONLY from core.gate truth
// tables. Each node yields an output signal (0/1) and a settle frame (topological layer). A feedback cycle is a
// clear error. Pure core: gate truth lives in core.gate; the only arithmetic here is frame/index bookkeeping.
//
// 성능: 노드의 입력 구동자를 매번 전선/노드 선형 탐색하면 O(노드^3)이라 큰 회로(RAM·CPU)가 느리다. 그래서
// 렌더마다 한 번 구동 인덱스(원0인덱스/원1인덱스: 각 노드의 in0/in1 구동자의 노드 인덱스, 없으면 -1)를
// 사전계산하고, 평가/정착 핫루프는 그 인덱스로 O(1) 조회한다. 알고리즘·결과는 그대로(순수 자료구조 최적화).
// Perf: resolving each node's input driver by scanning wires/nodes every pass is O(nodes^3) and makes big circuits
// (RAM/CPU) crawl. So once per render we precompute driver indices (원0인덱스/원1인덱스: the node index of each
// node's in0/in1 driver, -1 if none) and the eval/settle hot loops look up by index in O(1). Same algorithm and
// output, a pure data-structure speedup.
//
// 하우스룰(core): export는 타입/함수/불변 최상위만. 최상위(모듈 레벨) 가변·print·input·host 금지. 지역 let mut는 허용.
// House rule (core): export only types, functions, immutable top-level bindings; no module-level mutable state,
// no print, input, or host. A local `let mut` inside a function is fine (it stays pure).
//
// §17: 크로스모듈 타입은 네임스페이스 한정 참조(netlist.노드). / Cross-module types via namespace (netlist.노드).
//
// Key identifiers: 노드신호=nodeSignal, 시뮬결과=simResult, 평가회로=evaluate, 신호로=signalOf, 신호값=lookup,
// 구동맵=driverMap, 구동인덱스=buildDriverIndex, 인덱스맵=idToIndexMap, 원0인덱스/원1인덱스=in0/in1 driver index,
// 해결=resolved, 프레임=frame, 진전=progress, 막힘=stuck, 준비=ready, 남은수=remaining
import core.netlist
export type 노드신호 = { 아이디: string, 신호: bool, 프레임: int }
export type 시뮬결과 = { 신호들: Array<노드신호>, 오류: string, 프레임수: int }
// 순차 정착 결과: 시뮬 + 정착 상태(settled|oscillating|unsettled) + 마이크로스텝 수.
// Sequential settle result: the sim plus a settle status and the micro-step count.
export type 정착상태 = "settled" | "oscillating" | "unsettled"
export type 정착결과 = { 시뮬: 시뮬결과, 상태: 정착상태, 스텝: int }
// 구동 인덱스 맵: 노드 i의 in0/in1 입력을 구동하는 노드의 인덱스(없으면 -1). 노드 순서와 평행하다.
// Driver index map: for node i, the index of the node driving its in0/in1 input (-1 if none). Parallel to the node list.
type 구동맵 = { 원0: Array<int>, 원1: Array<int> }
// 구동 인덱스를 렌더당 한 번 만든다: 아이디→인덱스 맵(O(1) 조회)을 만든 뒤 각 전선의 끝/시작 노드를 인덱스로 풀어 원0/원1에 채운다.
// 선형 탐색이면 O(노드×전선)이라 큰 회로(RAM·CPU)가 느려서 Map으로 O(노드+전선)으로 만든다(결과 동일, 나중 전선이 이김).
// Build driver indices once per render: an id->index map (O(1) lookup), then resolve each wire's end/start node into 원0/원1.
// Linear scan would be O(nodes*wires) and crawl on big circuits, so a Map makes it O(nodes+wires) (same result, last wire wins).
function 구동인덱스(노드들: Array<netlist.노드>, 전선들: Array<netlist.전선>) -> 구동맵 {
let mut 인덱스맵: Map<string, int> = Map.new()
let mut 이 = 0
while 이 < 노드들.length { 인덱스맵.insert(노드들[이].아이디, 이); 이 = 이 + 1 }
let mut 원0: Array<int> = []
let mut 원1: Array<int> = []
let mut 제 = 0
while 제 < 노드들.length { 원0.push(-1); 원1.push(-1); 제 = 제 + 1 }
for 더블유 in 전선들 {
let 끝idx = 인덱스맵.get(더블유.끝노드) ?? -1
if 끝idx >= 0 {
let 시작idx = 인덱스맵.get(더블유.시작노드) ?? -1
if 더블유.끝핀 == "in0" { 원0[끝idx] = 시작idx }
else if 더블유.끝핀 == "in1" { 원1[끝idx] = 시작idx }
}
}
{ 원0: 원0, 원1: 원1 }
}
// 신호 배열에서 아이디로 조회(없으면 미해결 기본값 프레임 -1). / Look up by id (unresolved default frame -1 if absent).
function 신호값(신호들: Array<노드신호>, 아이디: string) -> 노드신호 {
let mut 결과 = { 아이디: "", 신호: false, 프레임: -1 }
for 에스 in 신호들 { if 에스.아이디 == 아이디 { 결과 = 에스 } }
결과
}
// 시뮬 결과에서 노드의 신호를 조회한다(ui가 색·점등에 쓴다). / Look up a node's signal (ui uses it to color/light).
export function 신호로(시뮬: 시뮬결과, 아이디: string) -> 노드신호 {
신호값(시뮬.신호들, 아이디)
}
// 회로를 평가한다. 위상 층마다 준비된 노드를 풀고, 더 못 풀면 순환으로 본다. 연결 안 된 입력은 0으로 본다.
// Evaluate the circuit. Each layer resolves ready nodes; if none can resolve, it is a cycle. An unconnected input is 0.
export function 평가회로(회로값: netlist.회로) -> 시뮬결과 {
let 노드들 = 회로값.노드들
let 구동 = 구동인덱스(노드들, 회로값.전선들)
let 원0인덱스 = 구동.원0
let 원1인덱스 = 구동.원1
let mut 신호들: Array<노드신호> = []
let mut 해결: Array<bool> = []
// 초기화: INPUT은 값으로 프레임 0에 확정, 나머지는 미해결. / Init: INPUT fixed at frame 0; others unresolved.
let mut 아이 = 0
while 아이 < 노드들.length {
let 엔 = 노드들[아이]
if netlist.입력노드인가(엔.종류) {
신호들.push({ 아이디: 엔.아이디, 신호: 엔.값 == 1, 프레임: 0 })
해결.push(true)
} else {
신호들.push({ 아이디: 엔.아이디, 신호: false, 프레임: -1 })
해결.push(false)
}
아이 = 아이 + 1
}
let mut 남은수 = 0
let mut 시 = 0
while 시 < 해결.length {
if !해결[시] { 남은수 = 남은수 + 1 }
시 = 시 + 1
}
let mut 막힘 = false
while 남은수 > 0 && !막힘 {
let mut 진전 = false
let mut 제이 = 0
while 제이 < 노드들.length {
if !해결[제이] {
let 엔 = 노드들[제이]
let 원0idx = 원0인덱스[제이]
let 원1idx = 원1인덱스[제이]
let 준비0 = 원0idx < 0 || 신호들[원0idx].프레임 >= 0
let 준비1 = 원1idx < 0 || 신호들[원1idx].프레임 >= 0
let 한입력 = netlist.입력수(엔.종류) <= 1
let 준비 = if 한입력 { 준비0 } else { 준비0 && 준비1 }
if 준비 {
let 값0 = if 원0idx < 0 { false } else { 신호들[원0idx].신호 }
let 값1 = if 원1idx < 0 { false } else { 신호들[원1idx].신호 }
let 출신호 = netlist.출력계산(엔.종류, 값0, 값1)
// 프레임 = 입력원 노드들의 최대 프레임 +1(게이트), OUTPUT은 원천 그대로. 연결 안 된 입력은 프레임 0.
// Frame = longest-path depth: max source frame +1 for a gate, the source frame for OUTPUT, 0 for unconnected.
let 원0프레임 = if 원0idx < 0 { 0 } else { 신호들[원0idx].프레임 }
let 원1프레임 = if 원1idx < 0 { 0 } else { 신호들[원1idx].프레임 }
let 깊은입력 = if 원0프레임 > 원1프레임 { 원0프레임 } else { 원1프레임 }
let 노드프레임 = if netlist.출력노드인가(엔.종류) { 원0프레임 } else if 한입력 { 원0프레임 + 1 } else { 깊은입력 + 1 }
신호들[제이] = { 아이디: 엔.아이디, 신호: 출신호, 프레임: 노드프레임 }
해결[제이] = true
남은수 = 남은수 - 1
진전 = true
}
}
제이 = 제이 + 1
}
if 남은수 > 0 && !진전 { 막힘 = true }
}
let 오류 = if 막힘 { "조합 회로에 순환이 있습니다(되먹임 루프는 지원하지 않습니다)" } else { "" }
// 프레임수 = 최대 노드 프레임 +1(애니메이션 층 수). / Frame count = max node frame +1 (animation layers).
let mut 최대프레임 = 0
let mut 케이 = 0
while 케이 < 신호들.length {
if 신호들[케이].프레임 > 최대프레임 { 최대프레임 = 신호들[케이].프레임 }
케이 = 케이 + 1
}
{ 신호들: 신호들, 오류: 오류, 프레임수: 최대프레임 + 1 }
}
// 한 마이크로스텝: 현재 스냅샷에서 모든 노드를 다시 계산한다(in-place 아님). INPUT은 값 고정, OUTPUT은 구동값, 게이트는 진리표.
// 구동 인덱스는 미리 만들어 넘긴다(매 스텝 재탐색 금지). / Driver indices are precomputed and passed in (no per-step rescan).
// One micro-step: recompute every node from the previous snapshot (never in-place). INPUT held, OUTPUT = its driver, gate = truth table.
function 한스텝(노드들: Array<netlist.노드>, 원0인덱스: Array<int>, 원1인덱스: Array<int>, 현재: Array<노드신호>) -> Array<노드신호> {
let mut 다음: Array<노드신호> = []
let mut 이 = 0
while 이 < 노드들.length {
let 엔 = 노드들[이]
if netlist.입력노드인가(엔.종류) {
다음.push({ 아이디: 엔.아이디, 신호: 엔.값 == 1, 프레임: 0 })
} else {
let 원0idx = 원0인덱스[이]
let 원1idx = 원1인덱스[이]
let 값0 = if 원0idx < 0 { false } else { 현재[원0idx].신호 }
let 값1 = if 원1idx < 0 { false } else { 현재[원1idx].신호 }
let 출 = netlist.출력계산(엔.종류, 값0, 값1)
다음.push({ 아이디: 엔.아이디, 신호: 출, 프레임: 0 })
}
이 = 이 + 1
}
다음
}
// 신호 벡터를 비교 가능한 문자열 키로(노드 순서가 일정하므로 키가 정렬된다). / Vector -> comparable key (node order is stable).
function 벡터키(신호들: Array<노드신호>) -> string {
let mut 키 = ""
for 에스 in 신호들 { 키 = 키 + (if 에스.신호 { "1" } else { "0" }) }
키
}
// 순차 정착(unit-delay 고정점). 비순환이면 위상 평가를 그대로 써서 조합 결과와 바이트 동일하다.
// 순환(되먹임)이면 이전 정착 상태에서 시작해 매 스텝 전 벡터를 한꺼번에 갱신하며 고정점까지 반복한다.
// 같은 벡터가 다시 나오면 발진(oscillating), 스텝 한도 초과면 미정착(unsettled), 변화가 멈추면 정착(settled).
// Sequential settle (unit-delay fixed point). Acyclic -> reuse the topological evaluator (byte-identical to combinational).
// Feedback -> start from the prior settled vector and recompute the whole vector each step until a fixed point.
// A repeated vector means oscillating, exceeding the step cap means unsettled, no change means settled.
export function 정착평가회로(회로값: netlist.회로, 이전상태: Array<노드신호>, 최대스텝: int) -> 정착결과 {
let 조합 = 평가회로(회로값)
if 조합.오류 == "" {
return { 시뮬: 조합, 상태: "settled", 스텝: 조합.프레임수 }
}
let 노드들 = 회로값.노드들
let 구동 = 구동인덱스(노드들, 회로값.전선들)
let 원0인덱스 = 구동.원0
let 원1인덱스 = 구동.원1
// 초기 스냅샷: INPUT은 현재 값, 나머지는 이전 정착 상태(없으면 cold-start 0). / Init: INPUT current value; others from prior state (cold-start 0).
let mut 현재: Array<노드신호> = []
let mut 마지막변경: Array<int> = []
for 엔 in 노드들 {
if netlist.입력노드인가(엔.종류) {
현재.push({ 아이디: 엔.아이디, 신호: 엔.값 == 1, 프레임: 0 })
} else {
현재.push({ 아이디: 엔.아이디, 신호: 신호값(이전상태, 엔.아이디).신호, 프레임: 0 })
}
마지막변경.push(0)
}
let mut 히스토리: Array<string> = [벡터키(현재)]
let mut 상태: 정착상태 = "unsettled"
let mut 스텝 = 0
let mut 끝 = false
while 스텝 < 최대스텝 && !끝 {
let 다음 = 한스텝(노드들, 원0인덱스, 원1인덱스, 현재)
스텝 = 스텝 + 1
let mut 아이 = 0
while 아이 < 다음.length {
if 다음[아이].신호 != 현재[아이].신호 { 마지막변경[아이] = 스텝 }
아이 = 아이 + 1
}
let 다음키 = 벡터키(다음)
if 다음키 == 벡터키(현재) {
상태 = "settled"
현재 = 다음
끝 = true
} else {
let mut 봤다 = false
for 헤 in 히스토리 { if 헤 == 다음키 { 봤다 = true } }
if 봤다 {
상태 = "oscillating"
현재 = 다음
끝 = true
} else {
히스토리.push(다음키)
현재 = 다음
}
}
}
// 프레임 = 마지막으로 값이 바뀐 스텝(정착 순서대로 애니메이션). / Frame = the last step a node changed (animate in settle order).
let mut 신호들: Array<노드신호> = []
let mut 최대프레임 = 0
let mut 제이 = 0
while 제이 < 현재.length {
신호들.push({ 아이디: 현재[제이].아이디, 신호: 현재[제이].신호, 프레임: 마지막변경[제이] })
if 마지막변경[제이] > 최대프레임 { 최대프레임 = 마지막변경[제이] }
제이 = 제이 + 1
}
{ 시뮬: { 신호들: 신호들, 오류: "", 프레임수: 최대프레임 + 1 }, 상태: 상태, 스텝: 스텝 }
}
// ===== core/hierarchy.tpz =====
// 계층(칩) 모델: 칩 = 핀을 가진 하위 넷리스트. 부모에 칩을 인스턴스로 놓고, 시뮬을 위해 원시 게이트로 평탄화한다.
// 평탄화는 경계(extin/extout 터미널)를 제거하고 외부 구동선과 내부 싱크를 직접 잇는다 → 칩 진리 = 펼친 진리.
// core는 순수: Host·UI 없음, 신호 산술 없음(게이트 진리는 core.gate). 좌표는 표시 메타데이터일 뿐이다.
// Hierarchy (chip) model: a chip is a sub-netlist with pins. A parent places chips as instances; for simulation
// they FLATTEN into primitive gates by removing the boundary (extin/extout terminals) and reconnecting the
// external driver straight to the internal sinks, so a chip's truth equals the flattened truth. Pure core.
//
// 하우스룰(core): export는 타입/함수/불변 최상위만. 최상위 가변·print·input·host 금지. 지역 let mut는 허용.
// House rule (core): export only types, functions, immutable top-level bindings; local let mut is fine (pure).
//
// §17: 크로스모듈 타입은 네임스페이스 한정 참조(netlist.노드). / Cross-module types via namespace (netlist.노드).
//
// 문법(본체 넷리스트 + 칩 정의):
// chipdef <이름> ... endchip 블록 안: node/wire 줄 + extin <외부핀> <내부아이디> + extout <외부핀> <내부아이디>
// chip <인스> <칩이름> <엑스> <와이> 부모에 칩 인스턴스(상자). 전선은 <인스>:<외부핀>으로 잇는다.
// view <인스> 현재 보는 칩(없으면 최상위). node/wire는 core.netlist 문법 그대로.
//
// Key identifiers: 외부핀=extPin, 칩정의=chipDef, 인스턴스=instance, 분해결과=decomposed, 평탄화=flatten,
// 분해=decompose, 정의찾기=defByName, 외부입력핀=extinPinOf, 외부출력핀=extoutPinOf, 구동자=driverOf,
// 터미널인가=isTerminal, 네임=namespaced
import core.netlist
import core.sim
import core.fault
import core.util
export type 외부핀 = { 핀: string, 노드: string }
// 본문 = 직렬화용 원시 정규 본체(평탄화 이전 구조: node/chip/wire/extin/extout). 중첩 칩의 round-trip을 위해 하위 인스턴스
// 구조를 보존한다(평탄 내부의 __ 네임스페이스 아이디는 재import에서 거부되므로). / 본문 = canonical raw body for round-trip
// serialization, preserving sub-instance structure (flat internals use __-namespaced ids that re-import rejects).
export type 칩정의 = { 이름: string, 내부: netlist.회로, 외부입력: Array<외부핀>, 외부출력: Array<외부핀>, 본문: string }
export type 인스턴스 = { 인스: string, 칩이름: string, 엑스: int, 와이: int }
export type 분해결과 = {
정의들: Array<칩정의>,
주노드들: Array<netlist.노드>,
주전선들: Array<netlist.전선>,
인스턴스들: Array<인스턴스>,
뷰: string,
오류: string,
}
// 이름으로 칩 정의를 찾는다(없으면 빈 정의). / Find a chip def by name (empty def if absent).
export function 정의찾기(정의들: Array<칩정의>, 이름: string) -> 칩정의 {
let mut 결과: 칩정의 = { 이름: "", 내부: { 노드들: [], 전선들: [] }, 외부입력: [], 외부출력: [], 본문: "" }
for 디 in 정의들 { if 디.이름 == 이름 { 결과 = 디 } }
결과
}
export function 정의있음(정의들: Array<칩정의>, 이름: string) -> bool {
정의찾기(정의들, 이름).이름 != ""
}
// 내부 노드 아이디가 어떤 외부입력 핀에 대응하는가(없으면 ""). / The extin pin name for an internal node id ("" if none).
function 외부입력핀(정의: 칩정의, 노드아이디: string) -> string {
let mut 결과 = ""
for 피 in 정의.외부입력 { if 피.노드 == 노드아이디 { 결과 = 피.핀 } }
결과
}
function 외부출력핀(정의: 칩정의, 노드아이디: string) -> string {
let mut 결과 = ""
for 피 in 정의.외부출력 { if 피.노드 == 노드아이디 { 결과 = 피.핀 } }
결과
}
function 터미널인가(정의: 칩정의, 노드아이디: string) -> bool {
외부입력핀(정의, 노드아이디) != "" || 외부출력핀(정의, 노드아이디) != ""
}
// 부모 전선 중 인스 I의 외부입력 핀 p를 구동하는 (노드,핀). 없으면 빈 끝점. / The external driver of instance pin I:p.
function 구동자(주전선들: Array<netlist.전선>, 인스: string, 핀: string) -> netlist.전선 {
let mut 결과: netlist.전선 = { 시작노드: "", 시작핀: "", 끝노드: "", 끝핀: "" }
for 더블유 in 주전선들 {
if 더블유.끝노드 == 인스 && 더블유.끝핀 == 핀 { 결과 = 더블유 }
}
결과
}
function 네임(인스: string, 아이디: string) -> string { 인스 + "__" + 아이디 }
// 내부 전선 중 extout 터미널 t를 구동하는 내부 소스 (노드,핀). / The internal source driving an extout terminal t.
function 터미널소스(전선들: Array<netlist.전선>, 터미널: string) -> netlist.전선 {
let mut 결과: netlist.전선 = { 시작노드: "", 시작핀: "", 끝노드: "", 끝핀: "" }
for 더블유 in 전선들 { if 더블유.끝노드 == 터미널 { 결과 = 더블유 } }
결과
}
function 인스턴스찾기(인스턴스들: Array<인스턴스>, 인스: string) -> bool {
let mut 있음 = false
for 아이 in 인스턴스들 { if 아이.인스 == 인스 { 있음 = true } }
있음
}
// 끝점(노드:핀)을 평탄 원시 소스로 해석한다. 칩 출력 핀이면 그 칩의 내부 소스로 내려가며, 칩→칩 체인을 추적한다.
// 원시 노드면 그대로 둔다. 이로써 한 칩의 출력이 다른 칩의 입력을 구동해도 평탄 전선이 끊기지 않는다.
// Resolve an endpoint (node:pin) to a flat primitive source, chasing through chip-output chains; a primitive
// stays as-is. This keeps flat wires intact when one chip's output drives another chip's input.
function 평탄끝점(분해: 분해결과, 노드: string, 핀: string) -> netlist.전선 {
let mut 엔 = 노드
let mut 피 = 핀
let mut 횟수 = 0
while 인스턴스찾기(분해.인스턴스들, 엔) && 횟수 < 64 {
let 디 = 정의찾기(분해.정의들, 분해의칩이름(분해.인스턴스들, 엔))
let mut 터미널노드 = ""
for 픽 in 디.외부출력 { if 픽.핀 == 피 { 터미널노드 = 픽.노드 } }
if 터미널노드 == "" {
엔 = ""; 피 = ""
} else {
let 소스 = 터미널소스(디.내부.전선들, 터미널노드)
if 소스.시작노드 == "" { 엔 = ""; 피 = "" } else { 엔 = 네임(엔, 소스.시작노드); 피 = 소스.시작핀 }
}
횟수 = 횟수 + 1
}
{ 시작노드: 엔, 시작핀: 피, 끝노드: "", 끝핀: "" }
}
// 평탄화: 본체(원시 노드/전선) + 칩 인스턴스를 펼쳐 하나의 원시 넷리스트로 만든다. core.sim이 이걸 평가한다.
// Flatten: expand the body (primitive nodes/wires) plus chip instances into one primitive netlist for core.sim.
export function 평탄화(분해: 분해결과) -> netlist.회로 {
let mut 노드들: Array<netlist.노드> = []
let mut 전선들: Array<netlist.전선> = []
// 1) 본체 원시 노드. / Body primitive nodes.
for 엔 in 분해.주노드들 { 노드들.push(엔) }
// 2) 칩 인스턴스의 내부 노드(터미널 제외)를 네임스페이스로 추가. / Instance internal nodes (minus terminals), namespaced.
for 아이 in 분해.인스턴스들 {
let 디 = 정의찾기(분해.정의들, 아이.칩이름)
for 엔 in 디.내부.노드들 {
if !터미널인가(디, 엔.아이디) {
노드들.push({ 아이디: 네임(아이.인스, 엔.아이디), 종류: 엔.종류, 엑스: 아이.엑스 + 엔.엑스, 와이: 아이.와이 + 엔.와이, 값: 엔.값 })
}
}
}
// 3) 본체 전선: 양 끝이 원시면 그대로. 칩 입력/출력 핀에 닿는 건 평탄화에서 재배선. / Body wires between primitives stay.
for 더블유 in 분해.주전선들 {
let 시작칩 = 인스턴스찾기(분해.인스턴스들, 더블유.시작노드)
let 끝칩 = 인스턴스찾기(분해.인스턴스들, 더블유.끝노드)
if !시작칩 && !끝칩 { 전선들.push(더블유) }
}
// 4) 각 인스턴스의 내부 전선을 재배선. / Reconnect each instance's internal wires.
for 아이 in 분해.인스턴스들 {
let 디 = 정의찾기(분해.정의들, 아이.칩이름)
for 아이더블유 in 디.내부.전선들 {
let 시작터미널 = 외부입력핀(디, 아이더블유.시작노드)
let 끝터미널 = 외부출력핀(디, 아이더블유.끝노드)
if 끝터미널 != "" {
// 이 전선은 칩 출력 q의 내부 소스를 정의한다. 평탄 전선이 아니다. 외부에서 따로 잇는다.
// This wire defines chip output q's internal source; reconnected from the external side below.
} else {
// 평탄 소스: 시작이 extin 터미널이면 외부 구동자, 아니면 내부 네임스페이스 out.
// Flat source: external driver if the start is an extin terminal, else the namespaced internal out.
let mut 소스노드 = 네임(아이.인스, 아이더블유.시작노드)
let mut 소스핀 = 아이더블유.시작핀
if 시작터미널 != "" {
let 구동 = 구동자(분해.주전선들, 아이.인스, 시작터미널)
let 해석 = 평탄끝점(분해, 구동.시작노드, 구동.시작핀)
소스노드 = 해석.시작노드
소스핀 = 해석.시작핀
}
if 소스노드 != "" {
전선들.push({ 시작노드: 소스노드, 시작핀: 소스핀, 끝노드: 네임(아이.인스, 아이더블유.끝노드), 끝핀: 아이더블유.끝핀 })
}
}
}
}
// 5) 칩 출력 핀에서 나가는 외부 전선을 내부 소스로 재배선. 끝이 다른 칩 입력이면 4)에서 이미 이었으니 건너뛴다.
// Reconnect external wires from chip output pins to the internal source; skip chip-input destinations (done in 4).
for 더블유 in 분해.주전선들 {
if 인스턴스찾기(분해.인스턴스들, 더블유.시작노드) && !인스턴스찾기(분해.인스턴스들, 더블유.끝노드) {
let 디 = 정의찾기(분해.정의들, 분해의칩이름(분해.인스턴스들, 더블유.시작노드))
// 외부출력 핀 시작핀 → 그 핀의 내부 터미널 노드. / extout pin -> its internal terminal node.
let mut 터미널노드 = ""
for 피 in 디.외부출력 { if 피.핀 == 더블유.시작핀 { 터미널노드 = 피.노드 } }
if 터미널노드 != "" {
let 소스 = 터미널소스(디.내부.전선들, 터미널노드)
if 소스.시작노드 != "" {
전선들.push({ 시작노드: 네임(더블유.시작노드, 소스.시작노드), 시작핀: 소스.시작핀, 끝노드: 더블유.끝노드, 끝핀: 더블유.끝핀 })
}
}
}
}
{ 노드들: 노드들, 전선들: 전선들 }
}
// 인스 아이디로 칩 이름. / The chip name for an instance id.
function 분해의칩이름(인스턴스들: Array<인스턴스>, 인스: string) -> string {
let mut 결과 = ""
for 아이 in 인스턴스들 { if 아이.인스 == 인스 { 결과 = 아이.칩이름 } }
결과
}
// 인스 아이디로 인스턴스를 찾는다(없으면 빈 인스턴스). / Find an instance by id.
export function 인스턴스로(분해: 분해결과, 인스: string) -> 인스턴스 {
let mut 결과: 인스턴스 = { 인스: "", 칩이름: "", 엑스: 0, 와이: 0 }
for 아이 in 분해.인스턴스들 { if 아이.인스 == 인스 { 결과 = 아이 } }
결과
}
// 칩 입력 핀의 신호를 담는 평탄 노드 아이디(= 그 핀을 구동하는 외부 노드). / Flat-sim node holding a chip input pin's signal.
export function 칩입력원(분해: 분해결과, 인스: string, 핀: string) -> string {
let 구동 = 구동자(분해.주전선들, 인스, 핀)
평탄끝점(분해, 구동.시작노드, 구동.시작핀).시작노드
}
// 칩 출력 핀의 신호를 담는 평탄 노드 아이디(= 내부 소스를 네임스페이스한 것). / Flat-sim node holding a chip output pin's signal.
export function 칩출력원(분해: 분해결과, 인스: string, 핀: string) -> string {
let 디 = 정의찾기(분해.정의들, 분해의칩이름(분해.인스턴스들, 인스))
let mut 터미널노드 = ""
for 피 in 디.외부출력 { if 피.핀 == 핀 { 터미널노드 = 피.노드 } }
if 터미널노드 == "" { return "" }
let 소스 = 터미널소스(디.내부.전선들, 터미널노드)
if 소스.시작노드 == "" { return "" }
네임(인스, 소스.시작노드)
}
// 칩 내부 뷰용 독립 회로: 칩 정의의 내부 넷리스트를 복제하되, extin 터미널(INPUT)의 값을 부모 경계 신호로 채운다.
// 이걸 따로 평가하면 진입 시 칩 내부의 신호가 부모에서 들어온 경계값과 일치한다.
// Standalone netlist for the chip's internal view: copy the chip's internal netlist but fill each extin terminal
// (an INPUT) with the parent boundary signal. Evaluated on its own, the internals match the values entering the chip.
export function 내부뷰회로(분해: 분해결과, 시뮬평탄: sim.시뮬결과, 인스: string) -> netlist.회로 {
let 인 = 인스턴스로(분해, 인스)
let 디 = 정의찾기(분해.정의들, 인.칩이름)
let mut 노드들: Array<netlist.노드> = []
for 엔 in 디.내부.노드들 {
let 핀 = 외부입력핀(디, 엔.아이디)
if 핀 != "" {
let 원 = 칩입력원(분해, 인스, 핀)
let 신호 = if 원 == "" { false } else { sim.신호로(시뮬평탄, 원).신호 }
노드들.push({ 아이디: 엔.아이디, 종류: 엔.종류, 엑스: 엔.엑스, 와이: 엔.와이, 값: if 신호 { 1 } else { 0 } })
} else {
노드들.push(엔)
}
}
{ 노드들: 노드들, 전선들: 디.내부.전선들 }
}
// 칩 정의의 본문 텍스트(node/chip/wire + extin/extout)를 이 레벨 본체로 파싱한다. extin/extout 줄은 건너뛴다
// (터미널은 node로 남고 매핑은 정의에서 가져온다). 본문은 칩본문이 만든 정규형이라 항상 잘 파싱된다.
// Parse a chip def's body text into this level's body. Skip extin/extout (terminals stay as nodes; the mapping
// comes from the def). The body is canonical (built by 칩본문), so it always parses.
function 본문분해(본문: string) -> { 주노드줄: string, 주전선들: Array<netlist.전선>, 인스턴스들: Array<인스턴스>, 오류: string } {
let mut 주노드줄 = ""
let mut 주전선들: Array<netlist.전선> = []
let mut 인스턴스들: Array<인스턴스> = []
let mut 오류 = ""
for 줄 in 본문.split("\n") {
let 토큰들 = util.빈칸제거(줄.trim().split(" "))
if 토큰들.length > 0 && 오류 == "" {
let 머리 = 토큰들[0]
if 머리 == "node" {
주노드줄 = 주노드줄 + 줄.trim() + "\n"
} else if 머리 == "chip" {
if 토큰들.length != 5 { 오류 = "chip 줄 형식 오류" }
else { 인스턴스들.push({ 인스: 토큰들[1], 칩이름: 토큰들[2], 엑스: toInt(토큰들[3]) ?? 0, 와이: toInt(토큰들[4]) ?? 0 }) }
} else if 머리 == "wire" {
if 토큰들.length != 3 { 오류 = "wire 줄 형식 오류" }
else {
let 시작 = 핀쪼개기(토큰들[1])
let 끝 = 핀쪼개기(토큰들[2])
if 시작.핀 == "" || 끝.핀 == "" { 오류 = "전선 끝점 형식 오류" }
else { 주전선들.push({ 시작노드: 시작.노드, 시작핀: 시작.핀, 끝노드: 끝.노드, 끝핀: 끝.핀 }) }
}
}
}
}
{ 주노드줄: 주노드줄, 주전선들: 주전선들, 인스턴스들: 인스턴스들, 오류: 오류 }
}
// 전역 정착 벡터를 평탄화된 레벨에 투영한다(되먹임이라 레벨 재평가가 순환에 막힐 때). INPUT은 노드값, 게이트·하위칩
// 내부는 <프리픽스>로컬의 전역 정착값(되먹임 보유 상태 충실), 전역에 없는 노드(레벨 자신의 extout 터미널)는 구동 전선
// 시작 노드 신호로 채운다. 재평가가 아니라 투영이라 칩 안 래치의 보유 상태가 깊은 줌에서도 충실하다.
// Project the global settled vector onto a flattened level (when the level re-eval is blocked by a feedback cycle).
// INPUT -> node value; gates and sub-chip internals -> the <prefix>local global settled value (faithful held state);
// nodes absent globally (the level's own extout terminals) -> the driving wire's source signal. Projection, not re-eval.
function 레벨투영(시뮬평탄: sim.시뮬결과, 프리픽스: string, 평탄레벨: netlist.회로) -> sim.시뮬결과 {
let mut 신호들: Array<sim.노드신호> = []
for 엔 in 평탄레벨.노드들 {
if netlist.입력노드인가(엔.종류) {
신호들.push({ 아이디: 엔.아이디, 신호: 엔.값 == 1, 프레임: 0 })
} else {
let 전역 = sim.신호로(시뮬평탄, 프리픽스 + 엔.아이디)
신호들.push({ 아이디: 엔.아이디, 신호: 전역.신호, 프레임: 전역.프레임 })
}
}
// 전역에 없던 노드(프레임 -1)는 구동 전선 시작 노드 신호로 채운다(extout 터미널 = 그 게이트 출력). / Fill globally-absent nodes from their driver.
let mut 아이 = 0
while 아이 < 신호들.length {
if 신호들[아이].프레임 < 0 {
let mut 구동 = ""
for 더블유 in 평탄레벨.전선들 {
if 더블유.끝노드 == 신호들[아이].아이디 { 구동 = 더블유.시작노드 }
}
let 소스 = sim.신호로({ 신호들: 신호들, 오류: "", 프레임수: 1 }, 구동)
신호들[아이] = { 아이디: 신호들[아이].아이디, 신호: 소스.신호, 프레임: 0 }
}
아이 = 아이 + 1
}
{ 신호들: 신호들, 오류: "", 프레임수: 1 }
}
// 뷰 경로(인스 체인)를 따라 내려가며 각 레벨을 독립 평가한다. 각 레벨: 그 칩의 본문을 본체(하위 칩 상자 포함)로 만들고
// extin 터미널을 부모 경계 신호로 채운 뒤 평탄화·평가한다(내부뷰회로의 다단계 일반화). 고장은 단계마다 한 겹씩
// 벗겨(내부고장) 적용해 각 레벨 시뮬이 충실하다. 마지막 레벨의 분해결과·시뮬을 그리기뷰로 그린다.
// Descend the view path (instance chain), evaluating each level standalone: build the chip's body (with sub-chip
// boxes), fill its extin terminals with the parent boundary signals, then flatten+evaluate (a multi-level
// generalization of 내부뷰회로). Faults are peeled one level per step (내부고장) so each level's sim is faithful.
export function 경로뷰(분해: 분해결과, 시뮬평탄: sim.시뮬결과, 경로: Array<string>, 고장들: Array<fault.고장>) -> { 뷰분해: 분해결과, 뷰시뮬: sim.시뮬결과, 이름들: Array<string>, 뷰고장들: Array<fault.고장>, 오류: string } {
let mut 현재분해 = 분해
let mut 현재시뮬 = 시뮬평탄
let mut 현재고장들 = 고장들
let mut 이름들: Array<string> = []
let mut 오류 = ""
let mut 프리픽스누적 = ""
let mut 인덱스 = 0
while 인덱스 < 경로.length && 오류 == "" {
let 인스 = 경로[인덱스]
if !인스턴스찾기(현재분해.인스턴스들, 인스) {
오류 = "뷰가 없는 인스턴스를 가리킵니다: " + 인스
} else {
let 인 = 인스턴스로(현재분해, 인스)
let 디 = 정의찾기(현재분해.정의들, 인.칩이름)
이름들.push(인.칩이름)
let 파스 = 본문분해(디.본문)
if 파스.오류 != "" { 오류 = 파스.오류 }
else {
let 검 = 본체검증(현재분해.정의들, 파스.주노드줄, 파스.주전선들, 파스.인스턴스들)
if 검.오류 != "" { 오류 = 검.오류 }
else {
// extin 터미널을 부모 경계 신호로 채운다(내부뷰회로와 동일). / Fill extin terminals with parent boundary signals.
let mut 채운노드들: Array<netlist.노드> = []
for 엔 in 검.주노드들 {
let 핀 = 외부입력핀(디, 엔.아이디)
if 핀 != "" {
let 원 = 칩입력원(현재분해, 인스, 핀)
let 신호 = if 원 == "" { false } else { sim.신호로(현재시뮬, 원).신호 }
채운노드들.push({ 아이디: 엔.아이디, 종류: 엔.종류, 엑스: 엔.엑스, 와이: 엔.와이, 값: if 신호 { 1 } else { 0 } })
} else { 채운노드들.push(엔) }
}
let 다음분해 = { 정의들: 현재분해.정의들, 주노드들: 채운노드들, 주전선들: 파스.주전선들, 인스턴스들: 파스.인스턴스들, 뷰: "", 오류: "" }
let 레벨고장들 = fault.내부고장(현재고장들, 인스)
let 평탄레벨 = fault.고장넷리스트(평탄화(다음분해), 레벨고장들)
// 조합 레벨이면 재평가(기존, 바이트 동일), 되먹임 레벨이면 전역 정착 벡터를 투영(칩 안 상태 충실).
// Acyclic level -> re-evaluate (unchanged, byte-identical); feedback level -> project the global settled vector (faithful in-chip state).
let 조합레벨 = sim.평가회로(평탄레벨)
프리픽스누적 = 프리픽스누적 + 인스 + "__"
현재시뮬 = if 조합레벨.오류 == "" { 조합레벨 } else { 레벨투영(시뮬평탄, 프리픽스누적, 평탄레벨) }
현재분해 = 다음분해
현재고장들 = 레벨고장들
}
}
}
인덱스 = 인덱스 + 1
}
{ 뷰분해: 현재분해, 뷰시뮬: 현재시뮬, 이름들: 이름들, 뷰고장들: 현재고장들, 오류: 오류 }
}
// 최상위 INPUT 노드 값을 한 틱의 입력상태로 덮어쓴 새 분해결과. 스크럽 시 그 틱의 회로를 렌더·평가하기 위함.
// A new 분해결과 with top-level INPUT values overridden by a tick's input-state, so a scrub renders/evaluates that tick.
export function 틱적용(분해: 분해결과, 상태: Array<{아이디: string, 값: int}>) -> 분해결과 {
let mut 새주노드들: Array<netlist.노드> = []
for 엔 in 분해.주노드들 {
if netlist.입력노드인가(엔.종류) {
let mut 값 = 엔.값
for 에스 in 상태 { if 에스.아이디 == 엔.아이디 { 값 = 에스.값 } }
새주노드들.push({ 아이디: 엔.아이디, 종류: 엔.종류, 엑스: 엔.엑스, 와이: 엔.와이, 값: 값 })
} else {
새주노드들.push(엔)
}
}
{ 정의들: 분해.정의들, 주노드들: 새주노드들, 주전선들: 분해.주전선들, 인스턴스들: 분해.인스턴스들, 뷰: 분해.뷰, 오류: 분해.오류 }
}
// "노드:핀" 분해(좋음/노드/핀). / Split "node:pin".
function 핀쪼개기(토큰: string) -> 외부핀 {
let 부분 = 토큰.split(":")
if 부분.length == 2 && 부분[0] != "" && 부분[1] != "" { { 핀: 부분[1], 노드: 부분[0] } }
else { { 핀: "", 노드: "" } }
}
// 노드 아이디가 회로에 있는가. / Is a node id present in a netlist?
function 노드존재(노드들: Array<netlist.노드>, 아이디: string) -> bool {
let mut 있음 = false
for 엔 in 노드들 { if 엔.아이디 == 아이디 { 있음 = true } }
있음
}
// extin/extout 핀이 정의에 있는가. / Is an extin/extout pin declared in the def?
function 외부입력핀있음(정의: 칩정의, 핀: string) -> bool {
let mut 있음 = false
for 피 in 정의.외부입력 { if 피.핀 == 핀 { 있음 = true } }
있음
}
function 외부출력핀있음(정의: 칩정의, 핀: string) -> bool {
let mut 있음 = false
for 피 in 정의.외부출력 { if 피.핀 == 핀 { 있음 = true } }
있음
}
// 평탄 회로에 중복 노드 아이디가 있으면 그 아이디 오류, 없으면 "". 계층 네임스페이스 충돌을 잡는다.
// A duplicate flattened node id (catches hierarchy namespace collisions); "" if none.
function 중복노드검출(회로: netlist.회로) -> string {
let mut 본: Array<string> = []
let mut 오류 = ""
for 엔 in 회로.노드들 {
let mut 봤나 = false
for 비 in 본 { if 비 == 엔.아이디 { 봤나 = true } }
if 봤나 && 오류 == "" { 오류 = "노드 아이디가 충돌합니다: " + 엔.아이디 }
본.push(엔.아이디)
}
오류
}
// 파싱 단계의 RAW 중첩 칩 정의(하위 칩 인스턴스를 담음). pre-flatten 패스가 이걸 flat 칩정의로 바꾼다.
// Raw nested chip def from parsing (carries sub-chip instances); the pre-flatten pass turns it into a flat 칩정의.
type 생칩정의 = { 이름: string, 노드줄: string, 전선들: Array<netlist.전선>, 인스턴스들: Array<인스턴스>, 외부입력: Array<외부핀>, 외부출력: Array<외부핀> }
// 본체(원시 노드 + 전선 + 칩 인스턴스)를 검증한다: 원시 끝점·핀·단일구동(netlist 파서), 인스턴스 정의 존재, 칩핀 전선의 실재 extin/extout.
// Validate a body: primitive endpoint/pin/single-driver (netlist parser), instance defs exist, chip-pin wires hit real extin/extout.
function 본체검증(정의들: Array<칩정의>, 주노드줄: string, 주전선들: Array<netlist.전선>, 인스턴스들: Array<인스턴스>) -> { 오류: string, 주노드들: Array<netlist.노드> } {
let mut 오류 = ""
let mut 주노드들: Array<netlist.노드> = []
let mut 검증줄 = 주노드줄
for 더블유 in 주전선들 {
let 시작칩 = 인스턴스찾기(인스턴스들, 더블유.시작노드)
let 끝칩 = 인스턴스찾기(인스턴스들, 더블유.끝노드)
if !시작칩 && !끝칩 {
검증줄 = 검증줄 + "wire " + 더블유.시작노드 + ":" + 더블유.시작핀 + " " + 더블유.끝노드 + ":" + 더블유.끝핀 + "\n"
}
}
let 노드파스 = netlist.파싱(검증줄)
if 노드파스.오류 != "" { 오류 = 노드파스.오류 }
else { 주노드들 = 노드파스.회로.노드들 }
if 오류 == "" {
for 아이 in 인스턴스들 { if 오류 == "" && !정의있음(정의들, 아이.칩이름) { 오류 = "정의되지 않은 칩입니다: " + 아이.칩이름 } }
}
if 오류 == "" {
for 더블유 in 주전선들 {
if 오류 == "" && 인스턴스찾기(인스턴스들, 더블유.시작노드) {
let 디 = 정의찾기(정의들, 분해의칩이름(인스턴스들, 더블유.시작노드))
if !외부출력핀있음(디, 더블유.시작핀) { 오류 = "없는 외부 핀입니다: " + 더블유.시작노드 + ":" + 더블유.시작핀 }
}
if 오류 == "" && 인스턴스찾기(인스턴스들, 더블유.끝노드) {
let 디 = 정의찾기(정의들, 분해의칩이름(인스턴스들, 더블유.끝노드))
if !외부입력핀있음(디, 더블유.끝핀) { 오류 = "없는 외부 핀입니다: " + 더블유.끝노드 + ":" + 더블유.끝핀 }
}
}
}
{ 오류: 오류, 주노드들: 주노드들 }
}
// 칩 정의 본문을 정규 텍스트로 만든다(원시 아이디·하위 인스턴스 보존; __ 네임스페이스 없음). 직렬화 round-trip용.
// Build a chip def's canonical body text (original ids + sub-instances preserved, no __), for round-trip serialization.
function 칩본문(주노드들: Array<netlist.노드>, 인스턴스들: Array<인스턴스>, 전선들: Array<netlist.전선>, 외부입력: Array<외부핀>, 외부출력: Array<외부핀>) -> string {
let mut 텍 = ""
for 엔 in 주노드들 {
텍 = 텍 + "node {엔.아이디} {엔.종류} {엔.엑스} {엔.와이}"
if netlist.입력노드인가(엔.종류) { 텍 = 텍 + " {엔.값}" }
텍 = 텍 + "\n"
}
for 아이 in 인스턴스들 { 텍 = 텍 + "chip {아이.인스} {아이.칩이름} {아이.엑스} {아이.와이}\n" }
for 더블유 in 전선들 { 텍 = 텍 + "wire {더블유.시작노드}:{더블유.시작핀} {더블유.끝노드}:{더블유.끝핀}\n" }
for 피 in 외부입력 { 텍 = 텍 + "extin {피.핀} {피.노드}\n" }
for 피 in 외부출력 { 텍 = 텍 + "extout {피.핀} {피.노드}\n" }
텍
}
// 입력 원문을 칩 정의 + 본체(원시 노드/전선) + 인스턴스 + 뷰로 분해한다. 첫 오류에서 멈춘다.
// Decompose the input into chip defs + body (primitive nodes/wires) + instances + view. Stops at the first error.
export function 분해(원문: string) -> 분해결과 {
let 줄들 = 원문.split("\n")
let mut 생정의들: Array<생칩정의> = []
let mut 주노드줄: string = ""
let mut 주전선들: Array<netlist.전선> = []
let mut 인스턴스들: Array<인스턴스> = []
let mut 뷰 = ""
let mut 오류 = ""
let mut 칩모드 = false
let mut 현재이름 = ""
let mut 현재노드줄 = ""
let mut 현재전선들: Array<netlist.전선> = []
let mut 현재인스턴스들: Array<인스턴스> = []
let mut 현재입력: Array<외부핀> = []
let mut 현재출력: Array<외부핀> = []
let mut 줄번호 = 0
while 줄번호 < 줄들.length {
if 오류 == "" {
let 토큰들 = util.빈칸제거(줄들[줄번호].trim().split(" "))
if 토큰들.length > 0 {
let 머리 = 토큰들[0]
if 칩모드 {
if 머리 == "endchip" {
생정의들.push({ 이름: 현재이름, 노드줄: 현재노드줄, 전선들: 현재전선들, 인스턴스들: 현재인스턴스들, 외부입력: 현재입력, 외부출력: 현재출력 })
칩모드 = false
} else if 머리 == "extin" && 토큰들.length == 3 {
if !util.안전아이디(토큰들[1]) { 오류 = "유효하지 않은 외부 핀 이름입니다: " + 토큰들[1] }
else { 현재입력.push({ 핀: 토큰들[1], 노드: 토큰들[2] }) }
} else if 머리 == "extout" && 토큰들.length == 3 {
if !util.안전아이디(토큰들[1]) { 오류 = "유효하지 않은 외부 핀 이름입니다: " + 토큰들[1] }
else { 현재출력.push({ 핀: 토큰들[1], 노드: 토큰들[2] }) }
} else if 머리 == "node" {
현재노드줄 = 현재노드줄 + 줄들[줄번호].trim() + "\n"
} else if 머리 == "wire" {
if 토큰들.length != 3 { 오류 = "wire 줄 형식은 wire 시작:핀 끝:핀 입니다" }
else {
let 시작 = 핀쪼개기(토큰들[1])
let 끝 = 핀쪼개기(토큰들[2])
if 시작.핀 == "" || 끝.핀 == "" { 오류 = "전선 끝점은 노드:핀 형식입니다" }
else { 현재전선들.push({ 시작노드: 시작.노드, 시작핀: 시작.핀, 끝노드: 끝.노드, 끝핀: 끝.핀 }) }
}
} else if 머리 == "chip" {
if 토큰들.length != 5 { 오류 = "chip 줄 형식은 chip 인스 칩이름 엑스 와이 입니다" }
else if !util.안전아이디(토큰들[1]) { 오류 = "유효하지 않은 인스턴스 아이디입니다: " + 토큰들[1] }
else if !util.안전아이디(토큰들[2]) { 오류 = "유효하지 않은 칩 이름입니다: " + 토큰들[2] }
else if !util.정수문자열(토큰들[3]) || !util.정수문자열(토큰들[4]) { 오류 = "칩 좌표는 정수여야 합니다" }
else { 현재인스턴스들.push({ 인스: 토큰들[1], 칩이름: 토큰들[2], 엑스: toInt(토큰들[3]) ?? 0, 와이: toInt(토큰들[4]) ?? 0 }) }
} else {
오류 = "칩 정의 안의 알 수 없는 줄입니다: " + 머리
}
} else if 머리 == "chipdef" {
if 토큰들.length != 2 { 오류 = "chipdef 줄 형식은 chipdef 이름 입니다" }
else if !util.안전아이디(토큰들[1]) { 오류 = "유효하지 않은 칩 이름입니다: " + 토큰들[1] }
else { 칩모드 = true; 현재이름 = 토큰들[1]; 현재노드줄 = ""; 현재전선들 = []; 현재인스턴스들 = []; 현재입력 = []; 현재출력 = [] }
} else if 머리 == "chip" {
if 토큰들.length != 5 { 오류 = "chip 줄 형식은 chip 인스 칩이름 엑스 와이 입니다" }
else if !util.안전아이디(토큰들[1]) { 오류 = "유효하지 않은 인스턴스 아이디입니다: " + 토큰들[1] }
else if !util.안전아이디(토큰들[2]) { 오류 = "유효하지 않은 칩 이름입니다: " + 토큰들[2] }
else if !util.정수문자열(토큰들[3]) || !util.정수문자열(토큰들[4]) { 오류 = "칩 좌표는 정수여야 합니다" }
else { 인스턴스들.push({ 인스: 토큰들[1], 칩이름: 토큰들[2], 엑스: toInt(토큰들[3]) ?? 0, 와이: toInt(토큰들[4]) ?? 0 }) }
} else if 머리 == "view" {
if 토큰들.length == 2 { 뷰 = 토큰들[1] }
} else if 머리 == "node" {
주노드줄 = 주노드줄 + 줄들[줄번호].trim() + "\n"
} else if 머리 == "wire" {
if 토큰들.length != 3 { 오류 = "wire 줄 형식은 wire 시작:핀 끝:핀 입니다" }
else {
let 시작 = 핀쪼개기(토큰들[1])
let 끝 = 핀쪼개기(토큰들[2])
if 시작.핀 == "" || 끝.핀 == "" { 오류 = "전선 끝점은 노드:핀 형식입니다" }
else { 주전선들.push({ 시작노드: 시작.노드, 시작핀: 시작.핀, 끝노드: 끝.노드, 끝핀: 끝.핀 }) }
}
} else {
오류 = "알 수 없는 줄입니다: " + 머리
}
}
}
줄번호 = 줄번호 + 1
}
if 오류 == "" && 칩모드 { 오류 = "endchip가 없습니다" }
// PRE-FLATTEN: 생정의들을 정의 순서로 평탄화해 flat 칩정의들로 만든다. 각 칩은 EARLIER flat 정의만 인스턴스화 가능
// (전방/자기 참조 = "정의되지 않은 칩" 오류 = 정의 사이클 차단). 그 후 모든 칩 내부가 원시라 기존 평탄화/뷰가 그대로 동작.
// Pre-flatten the raw defs in definition order; each may instantiate only EARLIER flat defs (forward/self ref errors,
// blocking definition cycles), so after the pass every chip's internal is flat primitives and the existing flatten/view work.
let mut 정의들: Array<칩정의> = []
let mut 깊이 = 0
while 깊이 < 생정의들.length && 오류 == "" {
let 생 = 생정의들[깊이]
let 검 = 본체검증(정의들, 생.노드줄, 생.전선들, 생.인스턴스들)
if 검.오류 != "" { 오류 = "칩 " + 생.이름 + ": " + 검.오류 }
else {
let mut 핀오류 = ""
for 피 in 생.외부입력 { if 핀오류 == "" && !노드존재(검.주노드들, 피.노드) { 핀오류 = "칩 " + 생.이름 + "의 extin " + 피.핀 + "이 없는 내부 노드를 가리킵니다: " + 피.노드 } }
for 피 in 생.외부출력 { if 핀오류 == "" && !노드존재(검.주노드들, 피.노드) { 핀오류 = "칩 " + 생.이름 + "의 extout " + 피.핀 + "이 없는 내부 노드를 가리킵니다: " + 피.노드 } }
if 핀오류 != "" { 오류 = 핀오류 }
else if 정의있음(정의들, 생.이름) { 오류 = "칩 정의가 중복됩니다: " + 생.이름 }
else {
let 바디 = { 정의들: 정의들, 주노드들: 검.주노드들, 주전선들: 생.전선들, 인스턴스들: 생.인스턴스들, 뷰: "", 오류: "" }
정의들.push({ 이름: 생.이름, 내부: 평탄화(바디), 외부입력: 생.외부입력, 외부출력: 생.외부출력, 본문: 칩본문(검.주노드들, 생.인스턴스들, 생.전선들, 생.외부입력, 생.외부출력) })
}
}
깊이 = 깊이 + 1
}
// 최상위 본체 검증(원시 끝점·핀·단일구동, 인스턴스 정의, 칩핀 전선). / Top-level body validation (same helper).
let mut 주노드들: Array<netlist.노드> = []
if 오류 == "" {
let 검 = 본체검증(정의들, 주노드줄, 주전선들, 인스턴스들)
if 검.오류 != "" { 오류 = 검.오류 }
else { 주노드들 = 검.주노드들 }
}
if 오류 == "" && 뷰 != "" && !인스턴스찾기(인스턴스들, 뷰) { 오류 = "뷰가 없는 인스턴스를 가리킵니다: " + 뷰 }
// 평탄화 후 중복 노드 아이디(인스 네임스페이스가 부모 노드와 충돌) 검출. / Post-flatten duplicate-id (namespace collision).
if 오류 == "" {
let 임시 = { 정의들: 정의들, 주노드들: 주노드들, 주전선들: 주전선들, 인스턴스들: 인스턴스들, 뷰: 뷰, 오류: "" }
오류 = 중복노드검출(평탄화(임시))
}
{ 정의들: 정의들, 주노드들: 주노드들, 주전선들: 주전선들, 인스턴스들: 인스턴스들, 뷰: 뷰, 오류: 오류 }
}
// ===== core/history.tpz =====
// 시간 이력과 파형. 조합 회로의 '시간'은 입력 변화의 순서다. 틱마다 그 틱의 입력상태로 평탄 회로를 다시
// 평가해 프로브(노드)의 신호를 모은다. 결정론이라 같은 틱은 항상 같은 신호를 낸다(정확한 스크럽의 토대).
// Time history and waveforms. For a combinational circuit, time is the sequence of input changes. Each tick
// re-evaluates the FLAT circuit with that tick's input-state and collects the probed node signals. Determinism
// makes a scrub exact: re-evaluating a tick always yields the same signals.
//
// 하우스룰(core): export는 타입/함수/불변 최상위만. 최상위 가변·print·input·host 금지. 지역 let mut는 허용.
// House rule (core): export only types, functions, immutable top-level bindings; no module-level mutable state,
// no print/input/host; a local `let mut` inside a function is fine.
//
// §17: 크로스모듈 타입은 네임스페이스 한정 참조(netlist.회로, sim.시뮬결과). / Cross-module types via namespace.
//
// Key identifiers: 입력값=inputValue, 파형=waveform, 이력결과=historyResult, 틱회로=tickCircuit,
// 파형계산=computeWaveforms, 상태=state, 값들=series, 프로브=probe, 틱시뮬들=tickSims
import core.netlist
import core.sim
// 한 틱의 입력상태(입력 노드 아이디 → 값). / One tick's input-state (input node id -> value).
export type 입력값 = { 아이디: string, 값: int }
// 한 프로브의 틱별 0/1 시리즈. / A probe's per-tick 0/1 series.
export type 파형 = { 프로브: string, 값들: Array<bool> }
export type 이력결과 = { 파형들: Array<파형>, 오류: string }
// 평탄 회로의 INPUT 값을 한 틱의 상태로 덮어쓴 새 회로(다른 노드는 그대로).
// A flat circuit with its INPUT values overridden by a tick's state (other nodes unchanged).
function 틱회로(평탄: netlist.회로, 상태: Array<입력값>) -> netlist.회로 {
let mut 새노드들: Array<netlist.노드> = []
for 엔 in 평탄.노드들 {
if netlist.입력노드인가(엔.종류) {
let mut 값 = 엔.값
for 에스 in 상태 { if 에스.아이디 == 엔.아이디 { 값 = 에스.값 } }
새노드들.push({ 아이디: 엔.아이디, 종류: 엔.종류, 엑스: 엔.엑스, 와이: 엔.와이, 값: 값 })
} else {
새노드들.push(엔)
}
}
{ 노드들: 새노드들, 전선들: 평탄.전선들 }
}
// 틱마다 한 번 평가하고, 프로브(노드)별로 틱 시리즈를 뽑는다. 평가는 core.sim(진리표)만 쓴다.
// Evaluate once per tick, then extract a per-tick series for each probe node. Evaluation uses only core.sim (truth tables).
export function 파형계산(평탄: netlist.회로, 이력: Array<Array<입력값>>, 프로브들: Array<string>) -> 이력결과 {
let mut 틱시뮬들: Array<sim.시뮬결과> = []
let mut 티 = 0
while 티 < 이력.length {
틱시뮬들.push(sim.평가회로(틱회로(평탄, 이력[티])))
티 = 티 + 1
}
let mut 파형들: Array<파형> = []
for 프 in 프로브들 {
let mut 값들: Array<bool> = []
let mut 시 = 0
while 시 < 틱시뮬들.length {
값들.push(sim.신호로(틱시뮬들[시], 프).신호)
시 = 시 + 1
}
파형들.push({ 프로브: 프, 값들: 값들 })
}
{ 파형들: 파형들, 오류: "" }
}
// ===== core/truthtable.tpz =====
// 진리표 생성: 평탄 회로의 입력(INPUT)들을 전수 열거해 각 조합의 출력(OUTPUT)을 엔진으로 계산한다.
// 각 칸은 core.gate 진리표를 거친 평가값이다(미리 적은 값이 아님) — "진짜 도는 기계" 증명의 토대.
// Truth-table generation: enumerate all INPUT combinations of the flat circuit and evaluate each OUTPUT with the
// engine. Every cell is an evaluation through core.gate truth tables (never a hardcoded literal) — the honesty proof.
//
// 하우스룰(core): export는 타입/함수/불변 최상위만. 최상위 가변·print·input·host 금지. 지역 let mut는 허용.
// House rule (core): export only types, functions, immutable top-level bindings; local let mut is fine (pure).
//
// §17: 크로스모듈 타입은 네임스페이스 한정 참조(netlist.회로). / Cross-module types via namespace (netlist.회로).
//
// Key identifiers: 진리행=row, 진리표결과=tableResult, 입력값들=inputBits, 출력값들=outputBits, 진리표=truthTable,
// 인덱스로=indexOf, 최대입력=maxInputs, 콤보=combo, 이승=placeValues, 케이=k
import core.netlist
import core.sim { 평가회로, 신호로 }
export type 진리행 = { 입력값들: Array<int>, 출력값들: Array<int> }
export type 진리표결과 = { 입력들: Array<string>, 출력들: Array<string>, 행들: Array<진리행>, 오류: string }
// 8개 초과면 256행을 넘으므로 전체 진리표를 만들지 않는다. / Beyond 8 inputs exceeds 256 rows; skip the full table.
let 최대입력 = 8
// 목록에서 아이디의 인덱스(없으면 -1). / Index of an id in a list (-1 if absent).
function 인덱스로(목록: Array<string>, 아이디: string) -> int {
let mut 결과 = -1
let mut 아이 = 0
while 아이 < 목록.length {
if 목록[아이] == 아이디 { 결과 = 아이 }
아이 = 아이 + 1
}
결과
}
// 평탄 회로의 입력 전수 조합을 열거해 출력을 평가한 진리표. / The truth table from enumerating the flat circuit's inputs.
export function 진리표(평탄: netlist.회로) -> 진리표결과 {
let mut 입력들: Array<string> = []
let mut 출력들: Array<string> = []
for 엔 in 평탄.노드들 {
if netlist.입력노드인가(엔.종류) { 입력들.push(엔.아이디) }
else if netlist.출력노드인가(엔.종류) { 출력들.push(엔.아이디) }
}
let 케이 = 입력들.length
if 케이 > 최대입력 {
return { 입력들: 입력들, 출력들: 출력들, 행들: [], 오류: "입력이 너무 많아 전체 진리표를 만들 수 없습니다(최대 8개)" }
}
// 2^k 조합 수와 각 입력의 자리값. / 2^k combo count and each input's place value.
let mut 총 = 1
let mut 이승: Array<int> = []
let mut 큐 = 0
while 큐 < 케이 { 이승.push(총); 총 = 총 * 2; 큐 = 큐 + 1 }
let mut 행들: Array<진리행> = []
let mut 콤보 = 0
while 콤보 < 총 {
// 이 조합의 입력 비트들(입력들 순서). / The input bits for this combo (in 입력들 order).
let mut 입력값들: Array<int> = []
let mut 아이 = 0
while 아이 < 케이 { 입력값들.push((콤보 / 이승[아이]) % 2); 아이 = 아이 + 1 }
// 입력값을 적용한 평탄 회로를 평가. / Evaluate the flat circuit with these input values applied.
let mut 새노드들: Array<netlist.노드> = []
for 엔 in 평탄.노드들 {
if netlist.입력노드인가(엔.종류) {
let 인덱스 = 인덱스로(입력들, 엔.아이디)
let 비트 = if 인덱스 >= 0 { 입력값들[인덱스] } else { 0 }
새노드들.push({ 아이디: 엔.아이디, 종류: "INPUT", 엑스: 엔.엑스, 와이: 엔.와이, 값: 비트 })
} else {
새노드들.push(엔)
}
}
let 시뮬 = 평가회로({ 노드들: 새노드들, 전선들: 평탄.전선들 })
let mut 출력값들: Array<int> = []
for 오 in 출력들 { 출력값들.push(if 신호로(시뮬, 오).신호 { 1 } else { 0 }) }
행들.push({ 입력값들: 입력값들, 출력값들: 출력값들 })
콤보 = 콤보 + 1
}
{ 입력들: 입력들, 출력들: 출력들, 행들: 행들, 오류: "" }
}
// ===== core/fault.tpz =====
// 고장 주입(stuck-at): 결함을 넣어 결과를 망가뜨려 "그림이 아니라 진짜 도는 기계"임을 증명한다(반증 가능성).
// 시뮬용으로 결함 노드를 그 고정값(0/1)만 내는 INPUT으로 바꾼다. 렌더는 원래 게이트를 고장색으로 표시한다.
// Fault injection (stuck-at): a fault breaks the result, proving the engine really runs (falsifiability). For the
// sim, a faulted node becomes an INPUT emitting only its stuck value (0/1); the renderer shows the original gate
// in the fault color. The stuck value bypasses gate truth, so this is not signal arithmetic.
//
// 하우스룰(core): export는 타입/함수/불변 최상위만. 최상위 가변·print·input·host 금지. 지역 let mut는 허용.
// House rule (core): export only types, functions, immutable top-level bindings; local let mut is fine (pure).
//
// §17: 크로스모듈 타입은 네임스페이스 한정 참조(netlist.노드). / Cross-module types via namespace (netlist.노드).
//
// 문법: fault <노드아이디> <stuck0|stuck1>
// Key identifiers: 고장=fault, 노드=node, 값=value, 고장파싱=parseFaults, 고장검증=validateFaults,
// 고장넷리스트=faultedCircuit, 고장노드들=faultIds, 노드존재=nodeExists, 고장파싱결과=parseResult
import core.netlist
export type 고장종류 = "stuck0" | "stuck1"
export type 고장 = { 노드: string, 값: bool }
export type 고장파싱결과 = { 고장들: Array<고장>, 오류: string }
function 고장종류유효(토큰: string) -> bool {
match 토큰 {
case "stuck0" => true
case "stuck1" => true
case _ => false
}
}
function 고장종류파싱(토큰: string) -> 고장종류 {
match 토큰 {
case "stuck0" => "stuck0"
case "stuck1" => "stuck1"
case _ => "stuck0"
}
}
function 고장값(종류: 고장종류) -> bool {
match 종류 {
case "stuck0" => false
case "stuck1" => true
}
}
// fault 줄들(토큰화됨)을 파싱한다. 형식 오류면 오류 메시지. / Parse tokenized fault lines; a format error returns a message.
export function 고장파싱(줄들: Array<Array<string>>) -> 고장파싱결과 {
let mut 고장들: Array<고장> = []
let mut 오류 = ""
for 토큰 in 줄들 {
if 토큰.length != 3 {
오류 = "fault 줄 형식은 fault <노드> <stuck0|stuck1> 입니다"
} else if !고장종류유효(토큰[2]) {
오류 = "고장 종류는 stuck0 또는 stuck1 이어야 합니다: " + 토큰[2]
} else {
let 종류: 고장종류 = 고장종류파싱(토큰[2])
고장들.push({ 노드: 토큰[1], 값: 고장값(종류) })
}
}
{ 고장들: 고장들, 오류: 오류 }
}
// 회로에 노드가 있는가. / Does the circuit contain a node id?
function 노드존재(노드들: Array<netlist.노드>, 아이디: string) -> bool {
let mut 있음 = false
for 엔 in 노드들 { if 엔.아이디 == 아이디 { 있음 = true } }
있음
}
// 고장 대상 노드가 (평탄) 회로에 존재하는지 검증. / Validate each fault node exists in the (flat) circuit.
export function 고장검증(고장들: Array<고장>, 노드들: Array<netlist.노드>) -> string {
let mut 오류 = ""
for 에프 in 고장들 {
if !노드존재(노드들, 에프.노드) { 오류 = "고장 대상 노드가 없습니다: " + 에프.노드 }
}
오류
}
// 결함 노드를 그 고정값을 내는 INPUT으로 바꾼 시뮬용 회로. / Sim circuit with faulted nodes turned into stuck-value INPUTs.
export function 고장넷리스트(회로: netlist.회로, 고장들: Array<고장>) -> netlist.회로 {
let mut 새노드들: Array<netlist.노드> = []
for 엔 in 회로.노드들 {
let mut 결함값 = -1
for 에프 in 고장들 { if 에프.노드 == 엔.아이디 { 결함값 = if 에프.값 { 1 } else { 0 } } }
if 결함값 >= 0 {
새노드들.push({ 아이디: 엔.아이디, 종류: "INPUT", 엑스: 엔.엑스, 와이: 엔.와이, 값: 결함값 })
} else {
새노드들.push(엔)
}
}
{ 노드들: 새노드들, 전선들: 회로.전선들 }
}
// 결함 노드 아이디 목록(렌더가 고장색으로 칠한다). / Faulted node ids (the renderer colors them).
export function 고장노드들(고장들: Array<고장>) -> Array<string> {
let mut 결과: Array<string> = []
for 에프 in 고장들 { 결과.push(에프.노드) }
결과
}
// 인스턴스 접두 "인스__"를 한 겹만 벗긴 고장 목록(중첩 뷰 한 단계 내려가기용). 나머지(부분[1..])는 더 깊은
// 네임스페이스일 수 있어 "__"로 다시 잇는다. 예: 인스="add"면 "add__f0__h1__x" → "f0__h1__x", "add__g" → "g".
// Peel one "인스__" level off each fault (descending one nesting level); the remainder may carry deeper namespaces.
export function 내부고장(고장들: Array<고장>, 인스: string) -> Array<고장> {
let mut 결과: Array<고장> = []
for 에프 in 고장들 {
let 부분 = 에프.노드.split("__")
if 부분.length >= 2 && 부분[0] == 인스 {
let mut 나머지 = ""
let mut 아이 = 1
while 아이 < 부분.length {
나머지 = if 나머지 == "" { 부분[아이] } else { 나머지 + "__" + 부분[아이] }
아이 = 아이 + 1
}
결과.push({ 노드: 나머지, 값: 에프.값 })
}
}
결과
}
// ===== ui/routing.tpz =====
// 직각(Manhattan) 자동 배선. 두 점을 가로·세로 선분만으로 잇는다(중간 엑스에서 수직으로 꺾는 Z 경로).
// ui는 core를 알지만 core는 ui를 모른다. 좌표 산술은 표시용이며 신호가 아니다.
// Orthogonal (Manhattan) routing: connect two points with horizontal and vertical segments only
// (a Z route that turns vertically at a mid-x). ui depends on core; core never depends on ui.
//
// Key identifiers: 점=point, 경로=route, 점열문자열=pointsString, 시작/끝=from/to, 중간=mid
export type 점 = { 엑스: int, 와이: int }
// 두 점을 직각으로 잇는 폴리라인 점열. 모든 선분은 가로 또는 세로다.
// An orthogonal polyline between two points. Every segment is horizontal or vertical.
export function 경로(시작엑스: int, 시작와이: int, 끝엑스: int, 끝와이: int) -> Array<점> {
let 중간 = (시작엑스 + 끝엑스) / 2
let mut 점들: Array<점> = []
점들.push({ 엑스: 시작엑스, 와이: 시작와이 })
점들.push({ 엑스: 중간, 와이: 시작와이 })
점들.push({ 엑스: 중간, 와이: 끝와이 })
점들.push({ 엑스: 끝엑스, 와이: 끝와이 })
점들
}
// 폴리라인 점열을 SVG points 속성 문자열로. / Polyline points to an SVG points attribute string.
export function 점열문자열(점들: Array<점>) -> string {
let mut 결과 = ""
let mut 인덱스 = 0
while 인덱스 < 점들.length {
if 인덱스 > 0 { 결과 = 결과 + " " }
결과 = 결과 + "{점들[인덱스].엑스},{점들[인덱스].와이}"
인덱스 = 인덱스 + 1
}
결과
}
// ===== ui/signalviz.tpz =====
// 신호 시각화: 신호값을 §1 색(활성=시그널, 비활성=흑연)으로, 정착 프레임을 data-frame 속성으로 바꾼다.
// 전선·핀은 원천(시작) 노드의 신호로 칠한다. JS는 data-frame 순서로 신호를 층층이 드러낸다(흐르는 펄스).
// Signal visualization: map a signal to a §1 color (active=signal, inactive=graphite) and a settle frame to a
// data-frame attribute. A wire/pin is colored by its source (from) node's signal. JS reveals signals layer by
// layer in data-frame order (the flowing pulse). ui depends on core; core never depends on ui.
//
// §17: 크로스모듈 타입은 네임스페이스 한정 참조(sim.시뮬결과). / Cross-module types via namespace (sim.시뮬결과).
//
// Key identifiers: 신호색=signalColor, 프레임속성=frameAttr, 원천색=sourceColor, 원천프레임=sourceFrame
import core.sim
let 시그널 = "#0FA3C7"
let 흑연 = "#9AA1A8"
// 신호값에 따른 색. 활성(1)=시그널, 비활성(0)=흑연. / Color by signal: active=signal, inactive=graphite.
export function 신호색(켜짐: bool) -> string {
if 켜짐 { 시그널 } else { 흑연 }
}
// 정착 프레임을 data-frame 속성으로. JS가 층 순서로 신호를 드러낼 때 쓴다. / Settle frame as a data-frame attribute.
export function 프레임속성(프레임: int) -> string {
" data-frame=\"{프레임}\""
}
// 전선/핀 색: 원천(시작) 노드의 신호로 칠한다. / Wire/pin color from the source (from) node's signal.
export function 원천색(시뮬: sim.시뮬결과, 원천노드: string) -> string {
신호색(sim.신호로(시뮬, 원천노드).신호)
}
// 전선의 정착 프레임: 원천 노드가 풀린 층(신호가 그 층에서 흘러나간다). / A wire's frame = its source node's layer.
export function 원천프레임(시뮬: sim.시뮬결과, 원천노드: string) -> string {
프레임속성(sim.신호로(시뮬, 원천노드).프레임)
}
// ===== ui/components.tpz =====
// 그래픽 입력/출력 컴포넌트: 입력은 만질 수 있는 토글(손잡이), 출력은 LED. 둘 다 회로가 계산한 신호로만
// 상태를 보인다(미리 적은 값 아님 — S5 정직성의 토대). 몸체만 그리고 핀은 render가 더한다(순환 의존 회피).
// Graphical I/O components: an input is a manipulable toggle (a knob); an output is an LED. Both reflect ONLY
// the circuit's computed signal (never a preset value, the basis of S5 honesty). These draw the BODY only; the
// render module adds the pin, so render and components do not depend on each other in a cycle.
//
// §17: 크로스모듈 타입은 네임스페이스 한정 참조(netlist.노드). / Cross-module types via namespace (netlist.노드).
//
// Key identifiers: 입력몸체=inputBody, 출력몸체=outputBody, 켜짐=on, 신호=signal, 손잡이=knob
import core.netlist
import ui.signalviz
let 먹 = "#15181C"
let 벨럼 = "#F6F7F4"
let 흑연 = "#9AA1A8"
let 라벨 = "#6b7178"
// 입력 토글의 몸체(핀 제외). 켜지면 손잡이가 오른쪽으로 가고 시그널 색으로 빛난다. data-input-id로 JS가 토글한다.
// The input toggle body (no pin). When on, the knob slides right and glows the signal color. data-input-id lets JS toggle it.
export function 입력몸체(엔: netlist.노드, 켜짐: bool) -> string {
let 엑스 = 엔.엑스
let 와이 = 엔.와이
let 손잡이엑스 = if 켜짐 { 엑스 + 27 } else { 엑스 + 13 }
let 손잡이색 = if 켜짐 { signalviz.신호색(true) } else { 흑연 }
let mut 에스 = "<g class=\"node input\" data-node-id=\"" + 엔.아이디 + "\" data-input-id=\"" + 엔.아이디 + "\" tabindex=\"0\" role=\"switch\">"
에스 = 에스 + "<rect x=\"{엑스}\" y=\"{와이}\" width=\"40\" height=\"30\" rx=\"15\" fill=\"" + 벨럼 + "\" stroke=\"" + 먹 + "\" stroke-width=\"2\" />"
에스 = 에스 + "<circle cx=\"{손잡이엑스}\" cy=\"{와이 + 15}\" r=\"9\" fill=\"" + 손잡이색 + "\" stroke=\"" + 먹 + "\" stroke-width=\"1.5\" />"
에스 = 에스 + "<text x=\"{엑스 + 20}\" y=\"{와이 - 5}\" text-anchor=\"middle\" font-size=\"10\" font-family=\"monospace\" fill=\"" + 라벨 + "\">" + 엔.아이디 + "</text>"
에스 + "</g>"
}
// 출력 LED의 몸체(핀 제외). 계산된 신호가 1이면 시그널 색으로 점등한다(미리 적은 값 아님 — 정직성).
// The output LED body (no pin). Lit the signal color when the COMPUTED signal is 1 (never a preset value, honesty).
export function 출력몸체(엔: netlist.노드, 신호: bool) -> string {
let 엑스 = 엔.엑스
let 와이 = 엔.와이
let 채움 = if 신호 { signalviz.신호색(true) } else { 벨럼 }
let mut 에스 = "<g class=\"node output\" data-node-id=\"" + 엔.아이디 + "\" data-output-id=\"" + 엔.아이디 + "\">"
에스 = 에스 + "<circle cx=\"{엑스 + 15}\" cy=\"{와이 + 15}\" r=\"14\" fill=\"" + 채움 + "\" stroke=\"" + 먹 + "\" stroke-width=\"2\" />"
에스 = 에스 + "<text x=\"{엑스 + 15}\" y=\"{와이 - 5}\" text-anchor=\"middle\" font-size=\"10\" font-family=\"monospace\" fill=\"" + 라벨 + "\">" + 엔.아이디 + "</text>"
에스 + "</g>"
}
// ===== ui/chrome.tpz =====
// 빵부스러기(브레드크럼): 현재 뷰 경로를 "회로 › 칩이름"으로 그린다. JS가 data-crumb로 레벨 이동을 처리한다.
// Breadcrumb: the current view path as "회로 › chipName". The JS handles level navigation via the data-crumb id.
//
// ui는 core를 알지만 core는 ui를 모른다. 칩 이름은 파서(core.hierarchy)가 안전 문자(영숫자·밑줄)로 검증하므로
// 속성·텍스트에 그대로 넣어도 안전하다. 깊은 중첩 뷰의 경로(칩 이름 체인)를 받아 단계별 빵부스러기를 만든다.
// ui depends on core; core never depends on ui. Chip names are parser-validated to safe characters, so they are
// safe in attributes and text. Takes the deep-view path (a chain of chip names) and renders a per-level breadcrumb.
//
// Key identifiers: 빵부스러기=breadcrumb, 이름들=names
// 뷰 경로 빵부스러기. 최상위는 "회로", 그 아래 각 레벨은 칩 이름. 각 조각은 data-crumb=깊이(0=최상위)를 달아
// JS가 그 깊이로 잘라 이동한다. / Breadcrumb for the view path: "회로" at top, then one crumb per nesting level.
// Each crumb carries data-crumb = depth (0 = top); the JS slices the path to that depth.
export function 빵부스러기(이름들: Array<string>) -> string {
let mut 에스 = "<nav class=\"crumbs\"><button class=\"crumb\" data-crumb=\"0\">회로</button>"
let mut 깊이 = 0
for 이름 in 이름들 {
깊이 = 깊이 + 1
에스 = 에스 + "<span class=\"crumb-sep\">›</span><button class=\"crumb\" data-crumb=\"{깊이}\">" + 이름 + "</button>"
}
에스 + "</nav>"
}
// ===== ui/waveform.tpz =====
// 파형(오실로스코프) 패널. 프로브마다 한 행, 틱에 따른 0/1 계단 트레이스를 그리고 현재 틱을 세로선으로 표시한다.
// §1 토큰만 쓴다: 먹(#15181C) 글자, 벨럼(#F6F7F4) 배경, 흑연(#9AA1A8) 비활성, 시그널(#0FA3C7) 활성.
// Waveform (oscilloscope) panel. One row per probe: a 0/1 step trace across ticks, with a vertical current-tick marker.
// Only §1 tokens: ink text, vellum background, graphite inactive, signal active. Pure: no Host, no arithmetic on signals.
//
// §17: 크로스모듈 타입은 네임스페이스(history.파형). / Cross-module types via namespace (history.파형).
//
// Key identifiers: 그리기파형=drawWaveforms, 라벨폭=labelWidth, 틱폭=tickWidth, 행높이=rowHeight, 현재틱=currentTick
import core.history
export function 그리기파형(파형들: Array<history.파형>, 현재틱: int) -> string {
if 파형들.length == 0 { return "" }
let 라벨폭 = 76
let 틱폭 = 16
let 행높이 = 40
let 위 = 16
let 틱수 = 파형들[0].값들.length
let 폭 = 라벨폭 + 틱수 * 틱폭 + 20
let 높이 = 위 + 파형들.length * 행높이 + 10
let mut 본문 = ""
// 현재 틱 세로 표시선. / Vertical marker at the current tick.
if 현재틱 >= 0 && 현재틱 < 틱수 {
let 엠엑스 = 라벨폭 + 현재틱 * 틱폭 + 틱폭 / 2
본문 = 본문 + "<line x1=\"{엠엑스}\" y1=\"0\" x2=\"{엠엑스}\" y2=\"{높이}\" stroke=\"#0FA3C7\" stroke-width=\"1\" stroke-dasharray=\"2 2\" />"
}
let mut 행 = 0
while 행 < 파형들.length {
let 파 = 파형들[행]
let 베이스 = 위 + 행 * 행높이
let 고 = 베이스 + 6
let 저 = 베이스 + 26
본문 = 본문 + "<text x=\"4\" y=\"{저}\" font-size=\"11\" font-family=\"monospace\" fill=\"#15181C\">{파.프로브}</text>"
let mut 티 = 0
while 티 < 파.값들.length {
let 엑스 = 라벨폭 + 티 * 틱폭
let 와이 = if 파.값들[티] { 고 } else { 저 }
let 색 = if 파.값들[티] { "#0FA3C7" } else { "#9AA1A8" }
본문 = 본문 + "<line x1=\"{엑스}\" y1=\"{와이}\" x2=\"{엑스 + 틱폭}\" y2=\"{와이}\" stroke=\"{색}\" stroke-width=\"2\" data-wave=\"{파.프로브}\" data-tick=\"{티}\" />"
if 티 + 1 < 파.값들.length {
let 다음와이 = if 파.값들[티 + 1] { 고 } else { 저 }
if 다음와이 != 와이 {
본문 = 본문 + "<line x1=\"{엑스 + 틱폭}\" y1=\"{와이}\" x2=\"{엑스 + 틱폭}\" y2=\"{다음와이}\" stroke=\"#9AA1A8\" stroke-width=\"1\" />"
}
}
티 = 티 + 1
}
행 = 행 + 1
}
"<svg viewBox=\"0 0 {폭} {높이}\" width=\"100%\" style=\"border:1px solid #d8dad6;background:#F6F7F4;margin-top:10px\" data-waveform=\"1\">" + 본문 + "</svg>"
}
// ===== ui/inspector.tpz =====
// 검사 패널: core.truthtable이 계산한 진리표를 표로 그린다. 활성 1은 시그널색, 고장 주입 중이면 고장 표시.
// 진리표 각 칸은 엔진(core.gate)이 계산한 값이라, 입력을 어떻게 넣어도 맞게 나옴을 눈으로 증명한다.
// Inspector panel: render the engine-computed truth table. Active 1 is the signal color; a fault note when faulted.
// Every cell is engine-computed, so it visibly proves the circuit is correct for any input.
//
// §17: 크로스모듈 타입은 네임스페이스 한정 참조(truthtable.진리표결과). / Cross-module types via namespace.
//
// Key identifiers: 그리기검사=drawInspector, 진리=truth, 고장있음=hasFault, 헤더=header, 본문=body, 색=color
import core.truthtable
let 시그널색 = "#0FA3C7"
let 흑연 = "#9AA1A8"
let 고장색 = "#C0392B"
// 진리표(+ 고장 표시)를 검사 패널로 그린다. 입력/출력 아이디는 파서가 안전문자로 검증해 그대로 둔다.
// Render the truth table (+ a fault note) as the inspector panel. Ids are validated safe chars by the parser.
export function 그리기검사(진리: truthtable.진리표결과, 고장있음: bool) -> string {
if 진리.오류 != "" {
return "<div class=\"inspector\" data-inspector=\"truth\"><div class=\"meta\">{진리.오류}</div></div>"
}
let mut 헤더 = "<tr>"
for 인 in 진리.입력들 { 헤더 = 헤더 + "<th>{인}</th>" }
헤더 = 헤더 + "<th class=\"sep\">→</th>"
for 오 in 진리.출력들 { 헤더 = 헤더 + "<th>{오}</th>" }
헤더 = 헤더 + "</tr>"
let mut 본문 = ""
for 행 in 진리.행들 {
본문 = 본문 + "<tr>"
for 비 in 행.입력값들 {
let 색 = if 비 == 1 { 시그널색 } else { 흑연 }
본문 = 본문 + "<td style=\"color:{색}\">{비}</td>"
}
본문 = 본문 + "<td class=\"sep\"></td>"
for 비 in 행.출력값들 {
let 색 = if 비 == 1 { 시그널색 } else { 흑연 }
본문 = 본문 + "<td style=\"color:{색}\">{비}</td>"
}
본문 = 본문 + "</tr>"
}
let 고장표시 = if 고장있음 {
"<div class=\"meta\" style=\"color:{고장색}\">고장 주입됨: 회로 출력이 진리표와 다를 수 있습니다.</div>"
} else { "" }
let 스타일 = "<style>.truth\{border-collapse:collapse;font-family:monospace;font-size:12px;margin-top:6px\}.truth th,.truth td\{padding:2px 9px;text-align:center\}.truth .sep\{color:#9AA1A8\}</style>"
"<div class=\"inspector\" data-inspector=\"truth\">" + 스타일 + "<div class=\"meta\">진리표(엔진이 계산)</div>" + 고장표시 + "<table class=\"truth\">" + 헤더 + 본문 + "</table></div>"
}
// ===== ui/render.tpz =====
// 넷리스트를 스키매틱 SVG로 그린다(§1 정체성: 먹/벨럼/흑연 + 시그널 한 색). 신호가 흐르는 전선과 출력 핀은
// core.sim이 계산한 값으로 칠하고, 입력 토글·출력 LED는 ui.components가, 신호 색·정착 프레임은 ui.signalviz가
// 맡는다. 모든 노드·핀·전선에 안정 data-* 아이디를 달아 JS가 히트 테스트하고 렌더 후 일시 상태를 다시 입힌다.
// Render a netlist as a schematic SVG (§1 identity: ink/vellum/graphite + one signal color). Signal-carrying
// wires and output pins are colored by core.sim's computed values; ui.components draws the input toggles and
// output LEDs; ui.signalviz supplies the signal colors and settle frames. Every node, pin, and wire carries a
// stable data-* id so the JS shell can hit-test and re-apply transient state after each render.
//
// ui는 core를 알지만 core는 ui를 모른다. 좌표 산술은 표시용이며 신호가 아니다(게이트 진리는 core.gate).
// 크로스모듈 타입은 네임스페이스 한정 참조(netlist.노드, routing.점, sim.시뮬결과) — §17: 타입은 값 import 불가.
// ui depends on core; core never depends on ui. Coordinate math is display-only, not signal (gate truth lives in
// core.gate). Cross-module types use namespace-qualified references (netlist.노드, routing.점, sim.시뮬결과).
//
// Key identifiers: 핀위치=pinPos, 핀점=pinDot, 노드로=nodeBy, 게이트그리기=drawGate, 노드그리기=drawNode,
// 전선그리기=drawWire, 그리기=draw, 격자=grid, 출색=outColor, 신호=signal
import core.netlist
import core.sim
import core.hierarchy
import ui.routing
import ui.signalviz
import ui.components
let 먹 = "#15181C"
let 벨럼 = "#F6F7F4"
let 흑연 = "#9AA1A8"
let 고장색 = "#C0392B"
type 노드역할 = "input" | "output" | "gate"
// 렌더링 역할은 닫힌 노드 kind에서만 나온다. 핀 이름은 칩 경계까지 열려 있어 문자열로 둔다.
// Rendering roles come only from closed node kinds. Pin names stay strings because chip boundaries are open.
function 역할로(종류: netlist.노드종류) -> 노드역할 {
match 종류 {
case "INPUT" => "input"
case "OUTPUT" => "output"
case "NAND" => "gate"
case "AND" => "gate"
case "OR" => "gate"
case "NOT" => "gate"
case "XOR" => "gate"
case "NOR" => "gate"
case "XNOR" => "gate"
}
}
// 노드가 고장 목록에 있는가(렌더가 고장색으로 외곽을 칠한다). / Is the node faulted (rendered with a fault-color outline)?
function 고장인가(고장들: Array<string>, 아이디: string) -> bool {
let mut 있음 = false
for 에프 in 고장들 { if 에프 == 아이디 { 있음 = true } }
있음
}
// 인스턴스 인스를 겨냥한 고장이 있는가(평탄 아이디 "인스__*"). 최상위 칩 상자를 고장색으로 칠할지 판단.
// Is any fault aimed at instance inst ("inst__*")? Decides whether to color the top-level chip box red.
function 인스고장인가(고장들: Array<string>, 인스: string) -> bool {
let 접두 = 인스 + "__"
let mut 있음 = false
for 에프 in 고장들 { if 에프.startsWith(접두) { 있음 = true } }
있음
}
// 핀의 연결점(몸체 바깥). 전선과 JS 히트 영역이 여기에 붙는다. / A pin's connection point (just outside the body).
function 핀위치(엔: netlist.노드, 핀: string) -> routing.점 {
let mut 점: routing.점 = { 엑스: 0, 와이: 0 }
match 역할로(엔.종류) {
case "input" => {
점 = { 엑스: 엔.엑스 + 46, 와이: 엔.와이 + 15 }
}
case "output" => {
점 = { 엑스: 엔.엑스 - 6, 와이: 엔.와이 + 15 }
}
case "gate" => {
if 핀 == "out" {
점 = { 엑스: 엔.엑스 + 76, 와이: 엔.와이 + 25 }
} else if 핀 == "in1" {
점 = { 엑스: 엔.엑스 - 6, 와이: 엔.와이 + 35 }
} else {
if netlist.입력수(엔.종류) == 2 { 점 = { 엑스: 엔.엑스 - 6, 와이: 엔.와이 + 15 } }
else { 점 = { 엑스: 엔.엑스 - 6, 와이: 엔.와이 + 25 } }
}
}
}
점
}
// 핀 점(히트 영역). 아이디·핀·종류는 파서가 검증한 안전 문자라 이스케이프가 필요 없다. 색은 신호로 칠한다.
// A pin dot (hit region). Id/pin/kind are parser-validated safe tokens, so no escaping is needed. Colored by signal.
function 핀점(아이디: string, 핀: string, 피엑스: int, 피와이: int, 색: string) -> string {
"<circle class=\"pin\" data-node-id=\"" + 아이디 + "\" data-pin=\"" + 핀 + "\" cx=\"{피엑스}\" cy=\"{피와이}\" r=\"4\" fill=\"" + 색 + "\" />"
}
function 게이트그리기(엔: netlist.노드, 시뮬: sim.시뮬결과, 고장들: Array<string>) -> string {
let 엑스 = 엔.엑스
let 와이 = 엔.와이
let 출색 = signalviz.신호색(sim.신호로(시뮬, 엔.아이디).신호)
let 외곽 = if 고장인가(고장들, 엔.아이디) { 고장색 } else { 먹 }
let mut 에스 = "<g class=\"node gate\" data-node-id=\"" + 엔.아이디 + "\">"
에스 = 에스 + "<rect x=\"{엑스}\" y=\"{와이}\" width=\"70\" height=\"50\" rx=\"4\" fill=\"" + 벨럼 + "\" stroke=\"" + 외곽 + "\" stroke-width=\"2\" />"
에스 = 에스 + "<text x=\"{엑스 + 35}\" y=\"{와이 + 29}\" text-anchor=\"middle\" font-size=\"13\" font-family=\"monospace\" fill=\"" + 외곽 + "\">" + 엔.종류 + "</text>"
let 출 = 핀위치(엔, "out")
에스 = 에스 + "<line x1=\"{엑스 + 70}\" y1=\"{출.와이}\" x2=\"{출.엑스}\" y2=\"{출.와이}\" stroke=\"" + 출색 + "\" stroke-width=\"2\" />"
에스 = 에스 + 핀점(엔.아이디, "out", 출.엑스, 출.와이, 출색)
let 인0 = 핀위치(엔, "in0")
에스 = 에스 + "<line x1=\"{인0.엑스}\" y1=\"{인0.와이}\" x2=\"{엑스}\" y2=\"{인0.와이}\" stroke=\"" + 흑연 + "\" stroke-width=\"2\" />"
에스 = 에스 + 핀점(엔.아이디, "in0", 인0.엑스, 인0.와이, 흑연)
if netlist.입력수(엔.종류) == 2 {
let 인1 = 핀위치(엔, "in1")
에스 = 에스 + "<line x1=\"{인1.엑스}\" y1=\"{인1.와이}\" x2=\"{엑스}\" y2=\"{인1.와이}\" stroke=\"" + 흑연 + "\" stroke-width=\"2\" />"
에스 = 에스 + 핀점(엔.아이디, "in1", 인1.엑스, 인1.와이, 흑연)
}
에스 + "</g>"
}
// 입력/출력은 ui.components가 몸체를, 여기서 핀을 더한다. 입력 핀은 자기 신호 색, 출력 핀은 흑연.
// components draws the I/O body; this adds the pin. The input pin takes the input's signal color, the output pin is graphite.
function 노드그리기(엔: netlist.노드, 시뮬: sim.시뮬결과, 고장들: Array<string>) -> string {
let mut 결과 = ""
match 역할로(엔.종류) {
case "input" => {
let 신호 = sim.신호로(시뮬, 엔.아이디).신호
let 색 = signalviz.신호색(신호)
let 출 = 핀위치(엔, "out")
let mut 에스 = components.입력몸체(엔, 신호)
에스 = 에스 + "<line x1=\"{엔.엑스 + 40}\" y1=\"{엔.와이 + 15}\" x2=\"{출.엑스}\" y2=\"{출.와이}\" stroke=\"" + 색 + "\" stroke-width=\"2\" />"
결과 = 에스 + 핀점(엔.아이디, "out", 출.엑스, 출.와이, 색)
}
case "output" => {
let 신호 = sim.신호로(시뮬, 엔.아이디).신호
let 인 = 핀위치(엔, "in0")
let mut 에스 = components.출력몸체(엔, 신호)
에스 = 에스 + "<line x1=\"{인.엑스}\" y1=\"{인.와이}\" x2=\"{엔.엑스}\" y2=\"{엔.와이 + 15}\" stroke=\"" + 흑연 + "\" stroke-width=\"2\" />"
결과 = 에스 + 핀점(엔.아이디, "in0", 인.엑스, 인.와이, 흑연)
}
case "gate" => {
결과 = 게이트그리기(엔, 시뮬, 고장들)
}
}
결과
}
// 검증된 회로에서 아이디로 노드를 찾는다(전선 양 끝은 파서가 존재를 보장한다). / Find a node by id in a validated circuit.
function 노드로(회로값: netlist.회로, 아이디: string) -> netlist.노드 {
let mut 결과: netlist.노드 = { 아이디: "", 종류: "INPUT", 엑스: 0, 와이: 0, 값: 0 }
for 엔 in 회로값.노드들 {
if 엔.아이디 == 아이디 { 결과 = 엔 }
}
결과
}
// 전선: 원천(시작) 노드의 신호로 칠하고 정착 프레임을 data-frame에 담는다(JS가 층 순서로 흐름을 드러낸다).
// A wire: colored by its source node's signal, with the settle frame in data-frame (JS reveals flow by layer).
function 전선그리기(회로값: netlist.회로, 시뮬: sim.시뮬결과, 시작노드: string, 시작핀: string, 끝노드: string, 끝핀: string) -> string {
let 에이 = 핀위치(노드로(회로값, 시작노드), 시작핀)
let 비 = 핀위치(노드로(회로값, 끝노드), 끝핀)
let 점들 = routing.경로(에이.엑스, 에이.와이, 비.엑스, 비.와이)
let 색 = signalviz.원천색(시뮬, 시작노드)
let 프레임 = signalviz.원천프레임(시뮬, 시작노드)
// 안정 아이디 = 도착 엔드포인트(입력핀은 전선 하나만 받도록 검증됨). 렌더 순서와 무관하다.
// Stable id = the destination endpoint (an input pin takes at most one wire). Order-independent.
"<polyline class=\"wire\" data-wire-id=\"" + 끝노드 + ":" + 끝핀 + "\"" + 프레임 + " points=\"" + routing.점열문자열(점들) + "\" fill=\"none\" stroke=\"" + 색 + "\" stroke-width=\"2\" />"
}
function 격자(엑스: int, 와이: int, 폭: int, 높이: int) -> string {
"<defs><pattern id=\"grid\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\"><path d=\"M20 0H0V20\" fill=\"none\" stroke=\"" + 먹 + "\" stroke-opacity=\"0.05\" stroke-width=\"1\" /></pattern></defs><rect x=\"{엑스}\" y=\"{와이}\" width=\"{폭}\" height=\"{높이}\" fill=\"url(#grid)\" />"
}
// 회로 전체를 SVG로. 전선을 먼저 깔고 노드를 위에 그린다. 크기는 노드 경계 상자(음수 좌표·팬 포함)로 정한다.
// Render the whole circuit as SVG. Wires first, nodes on top. Size is the node bounding box (covers negatives/panning).
export function 그리기(회로값: netlist.회로, 시뮬: sim.시뮬결과, 고장들: Array<string>) -> string {
let mut 최소엑스 = 0
let mut 최소와이 = 0
let mut 최대엑스 = 320
let mut 최대와이 = 200
let mut 첫 = true
for 엔 in 회로값.노드들 {
let 왼 = 엔.엑스 - 12
let 위 = 엔.와이 - 22
let 오른 = 엔.엑스 + 86
let 아래 = 엔.와이 + 62
if 첫 {
최소엑스 = 왼; 최소와이 = 위; 최대엑스 = 오른; 최대와이 = 아래; 첫 = false
} else {
if 왼 < 최소엑스 { 최소엑스 = 왼 }
if 위 < 최소와이 { 최소와이 = 위 }
if 오른 > 최대엑스 { 최대엑스 = 오른 }
if 아래 > 최대와이 { 최대와이 = 아래 }
}
}
let 폭 = 최대엑스 - 최소엑스
let 높이 = 최대와이 - 최소와이
let mut 본문 = 격자(최소엑스, 최소와이, 폭, 높이)
for 더블유 in 회로값.전선들 {
본문 = 본문 + 전선그리기(회로값, 시뮬, 더블유.시작노드, 더블유.시작핀, 더블유.끝노드, 더블유.끝핀)
}
for 엔 in 회로값.노드들 {
본문 = 본문 + 노드그리기(엔, 시뮬, 고장들)
}
"<svg class=\"schematic\" viewBox=\"{최소엑스} {최소와이} {폭} {높이}\" width=\"{폭}\" height=\"{높이}\" xmlns=\"http://www.w3.org/2000/svg\">" + 본문 + "</svg>"
}
// ── S3 계층 뷰: 최상위에서 칩 인스턴스를 블랙박스로, 경계 핀을 평탄 시뮬 신호로 그린다. ──
// S3 hierarchy view: at the top level a chip instance renders as a black box, its boundary pins colored by the flat sim.
let 칩폭 = 96
// 외부 핀 목록에서 핀 이름의 순서(없으면 -1). / The ordinal of a pin name in an external-pin list (-1 if absent).
function 외부핀인덱스(핀들: Array<hierarchy.외부핀>, 핀: string) -> int {
let mut 결과 = -1
let mut 인덱스 = 0
for 피 in 핀들 {
if 피.핀 == 핀 { 결과 = 인덱스 }
인덱스 = 인덱스 + 1
}
결과
}
// 칩 박스 높이(입력·출력 핀 수의 큰 쪽 기준). / Chip box height (by the larger of the extin/extout counts).
function 칩높이(정의: hierarchy.칩정의) -> int {
let 입력수칩 = 정의.외부입력.length
let 출력수칩 = 정의.외부출력.length
let 핀줄 = if 입력수칩 > 출력수칩 { 입력수칩 } else { 출력수칩 }
핀줄 * 30 + 16
}
// 칩 인스턴스 핀의 연결점. extin은 왼쪽(엑스-6), extout은 오른쪽(엑스+칩폭+6). / A chip pin's connection point.
function 칩핀위치(인스턴스: hierarchy.인스턴스, 정의: hierarchy.칩정의, 핀: string) -> routing.점 {
let 입력인덱스 = 외부핀인덱스(정의.외부입력, 핀)
if 입력인덱스 >= 0 {
{ 엑스: 인스턴스.엑스 - 6, 와이: 인스턴스.와이 + 30 + 입력인덱스 * 30 }
} else {
let 출력인덱스 = 외부핀인덱스(정의.외부출력, 핀)
{ 엑스: 인스턴스.엑스 + 칩폭 + 6, 와이: 인스턴스.와이 + 30 + 출력인덱스 * 30 }
}
}
// 끝점(노드:핀)의 연결점. 칩 인스턴스면 칩핀위치, 원시 노드면 핀위치. / Endpoint position (chip pin or primitive pin).
function 끝점위치(분해: hierarchy.분해결과, 노드: string, 핀: string) -> routing.점 {
let 인 = hierarchy.인스턴스로(분해, 노드)
if 인.인스 != "" {
칩핀위치(인, hierarchy.정의찾기(분해.정의들, 인.칩이름), 핀)
} else {
핀위치(노드로({ 노드들: 분해.주노드들, 전선들: 분해.주전선들 }, 노드), 핀)
}
}
// 전선 시작의 평탄 시뮬 노드 아이디(색·프레임용). 칩 출력 핀이면 칩출력원, 원시면 그 노드. / The flat-sim source id of a wire.
function 평탄소스(분해: hierarchy.분해결과, 노드: string, 핀: string) -> string {
let 인 = hierarchy.인스턴스로(분해, 노드)
if 인.인스 != "" { hierarchy.칩출력원(분해, 노드, 핀) } else { 노드 }
}
// 칩 인스턴스를 블랙박스로. 굵은 외곽선 + 보조선 + 칩 이름(크롬 폰트) + extin(왼)·extout(오른) 핀(경계 신호 색). data-chip-id.
// A chip instance as a black box: bold outline + inner rule + chip name + extin/extout pins colored by boundary signals.
function 칩상자그리기(인스턴스: hierarchy.인스턴스, 정의: hierarchy.칩정의, 분해: hierarchy.분해결과, 시뮬: sim.시뮬결과, 고장들: Array<string>) -> string {
let 엑스 = 인스턴스.엑스
let 와이 = 인스턴스.와이
let 높이 = 칩높이(정의)
// 내부 게이트 중 하나라도 고장이면 박스를 고장색으로(반증 가능성 시각화). / Red box if any internal gate is faulted.
let 외곽 = if 인스고장인가(고장들, 인스턴스.인스) { 고장색 } else { 먹 }
let mut 에스 = "<g class=\"node chip\" data-chip-id=\"" + 인스턴스.인스 + "\">"
에스 = 에스 + "<rect x=\"{엑스}\" y=\"{와이}\" width=\"{칩폭}\" height=\"{높이}\" rx=\"5\" fill=\"" + 벨럼 + "\" stroke=\"" + 외곽 + "\" stroke-width=\"3\" />"
에스 = 에스 + "<rect x=\"{엑스 + 4}\" y=\"{와이 + 4}\" width=\"{칩폭 - 8}\" height=\"{높이 - 8}\" rx=\"3\" fill=\"none\" stroke=\"" + 흑연 + "\" stroke-width=\"1\" />"
에스 = 에스 + "<text x=\"{엑스 + 칩폭 / 2}\" y=\"{와이 + 높이 / 2 + 4}\" text-anchor=\"middle\" font-size=\"12\" font-family=\"sans-serif\" fill=\"" + 외곽 + "\">" + 인스턴스.칩이름 + "</text>"
for 피 in 정의.외부입력 {
let 위 = 칩핀위치(인스턴스, 정의, 피.핀)
let 원 = hierarchy.칩입력원(분해, 인스턴스.인스, 피.핀)
let 색 = signalviz.신호색(if 원 == "" { false } else { sim.신호로(시뮬, 원).신호 })
에스 = 에스 + "<line x1=\"{위.엑스}\" y1=\"{위.와이}\" x2=\"{엑스}\" y2=\"{위.와이}\" stroke=\"" + 색 + "\" stroke-width=\"2\" />"
에스 = 에스 + "<text x=\"{엑스 + 6}\" y=\"{위.와이 + 4}\" font-size=\"9\" font-family=\"monospace\" fill=\"" + 흑연 + "\">" + 피.핀 + "</text>"
에스 = 에스 + 핀점(인스턴스.인스, 피.핀, 위.엑스, 위.와이, 색)
}
for 피 in 정의.외부출력 {
let 위 = 칩핀위치(인스턴스, 정의, 피.핀)
let 원 = hierarchy.칩출력원(분해, 인스턴스.인스, 피.핀)
let 색 = signalviz.신호색(if 원 == "" { false } else { sim.신호로(시뮬, 원).신호 })
에스 = 에스 + "<line x1=\"{엑스 + 칩폭}\" y1=\"{위.와이}\" x2=\"{위.엑스}\" y2=\"{위.와이}\" stroke=\"" + 색 + "\" stroke-width=\"2\" />"
에스 = 에스 + "<text x=\"{엑스 + 칩폭 - 6}\" y=\"{위.와이 + 4}\" text-anchor=\"end\" font-size=\"9\" font-family=\"monospace\" fill=\"" + 흑연 + "\">" + 피.핀 + "</text>"
에스 = 에스 + 핀점(인스턴스.인스, 피.핀, 위.엑스, 위.와이, 색)
}
에스 + "</g>"
}
// 칩을 포함한 전선. 양 끝이 원시 노드든 칩 핀이든 위치를 구해 직각 배선하고, 시작 신호 색·정착 프레임을 단다.
// A wire that may touch chips: positions for primitive or chip pins, orthogonal route, colored/framed by the source.
function 전선그리기2(분해: hierarchy.분해결과, 시뮬: sim.시뮬결과, 시작노드: string, 시작핀: string, 끝노드: string, 끝핀: string) -> string {
let 에이 = 끝점위치(분해, 시작노드, 시작핀)
let 비 = 끝점위치(분해, 끝노드, 끝핀)
let 점들 = routing.경로(에이.엑스, 에이.와이, 비.엑스, 비.와이)
let 소스 = 평탄소스(분해, 시작노드, 시작핀)
let 색 = signalviz.원천색(시뮬, 소스)
let 프레임 = signalviz.원천프레임(시뮬, 소스)
"<polyline class=\"wire\" data-wire-id=\"" + 끝노드 + ":" + 끝핀 + "\"" + 프레임 + " points=\"" + routing.점열문자열(점들) + "\" fill=\"none\" stroke=\"" + 색 + "\" stroke-width=\"2\" />"
}
// 최상위 계층 뷰: 본체 원시 노드 + 칩 상자 + 전선. 크기는 둘을 모두 덮는 경계 상자.
// Top-level hierarchy view: body primitives + chip boxes + wires, sized to a bounding box covering both.
export function 그리기뷰(분해: hierarchy.분해결과, 시뮬: sim.시뮬결과, 고장들: Array<string>) -> string {
let mut 최소엑스 = 0
let mut 최소와이 = 0
let mut 최대엑스 = 320
let mut 최대와이 = 200
let mut 첫 = true
for 엔 in 분해.주노드들 {
let 왼 = 엔.엑스 - 12
let 위 = 엔.와이 - 22
let 오른 = 엔.엑스 + 86
let 아래 = 엔.와이 + 62
if 첫 { 최소엑스 = 왼; 최소와이 = 위; 최대엑스 = 오른; 최대와이 = 아래; 첫 = false }
else {
if 왼 < 최소엑스 { 최소엑스 = 왼 }
if 위 < 최소와이 { 최소와이 = 위 }
if 오른 > 최대엑스 { 최대엑스 = 오른 }
if 아래 > 최대와이 { 최대와이 = 아래 }
}
}
for 아이 in 분해.인스턴스들 {
let 디 = hierarchy.정의찾기(분해.정의들, 아이.칩이름)
let 왼 = 아이.엑스 - 16
let 위 = 아이.와이 - 12
let 오른 = 아이.엑스 + 칩폭 + 16
let 아래 = 아이.와이 + 칩높이(디) + 12
if 첫 { 최소엑스 = 왼; 최소와이 = 위; 최대엑스 = 오른; 최대와이 = 아래; 첫 = false }
else {
if 왼 < 최소엑스 { 최소엑스 = 왼 }
if 위 < 최소와이 { 최소와이 = 위 }
if 오른 > 최대엑스 { 최대엑스 = 오른 }
if 아래 > 최대와이 { 최대와이 = 아래 }
}
}
let 폭 = 최대엑스 - 최소엑스
let 높이 = 최대와이 - 최소와이
let mut 본문 = 격자(최소엑스, 최소와이, 폭, 높이)
for 더블유 in 분해.주전선들 {
본문 = 본문 + 전선그리기2(분해, 시뮬, 더블유.시작노드, 더블유.시작핀, 더블유.끝노드, 더블유.끝핀)
}
for 엔 in 분해.주노드들 {
본문 = 본문 + 노드그리기(엔, 시뮬, 고장들)
}
for 아이 in 분해.인스턴스들 {
본문 = 본문 + 칩상자그리기(아이, hierarchy.정의찾기(분해.정의들, 아이.칩이름), 분해, 시뮬, 고장들)
}
"<svg class=\"schematic\" viewBox=\"{최소엑스} {최소와이} {폭} {높이}\" width=\"{폭}\" height=\"{높이}\" xmlns=\"http://www.w3.org/2000/svg\">" + 본문 + "</svg>"
}
// ===== app/serialize.tpz =====
// 회로 직렬화: 분해결과를 정규 텍스트(version 1 + 칩정의 + 본체 노드/전선 + 인스턴스)로 내보낸다.
// 배열 순서를 그대로 보존하므로 파서가 같은 회로로 다시 읽고, 내보내기→가져오기→내보내기가 바이트 동일하다(멱등).
// Circuit serialization: emit the decomposed circuit as canonical text (version + chipdefs + body nodes/wires +
// instances). Array order is preserved, so the parser reads back the SAME circuit and export -> import -> export is
// byte-identical (idempotent).
//
// 하우스룰(app): export는 함수만(순수). 신호 산술 없음(좌표·값은 표시 메타데이터). §17: 타입은 네임스페이스 참조.
// House rule (app/serialize): pure functions only; no signal arithmetic (coords/values are display metadata);
// cross-module types are namespace-qualified.
//
// Key identifiers: 직렬화=serialize, 노드줄=nodeLine, 전선줄=wireLine
import core.netlist
import core.hierarchy
// 노드 한 줄. INPUT만 값 토큰을 붙인다(다른 종류에 값 토큰을 붙이면 파서가 거부하므로).
// One node line. Only INPUT carries a value token (the parser rejects a value token on other kinds).
function 노드줄(엔: netlist.노드) -> string {
if netlist.입력노드인가(엔.종류) {
"node {엔.아이디} {엔.종류} {엔.엑스} {엔.와이} {엔.값}"
} else {
"node {엔.아이디} {엔.종류} {엔.엑스} {엔.와이}"
}
}
// 전선 한 줄. / One wire line.
function 전선줄(더블유: netlist.전선) -> string {
"wire {더블유.시작노드}:{더블유.시작핀} {더블유.끝노드}:{더블유.끝핀}"
}
// 분해결과 → 정규 텍스트. version 헤더는 파서가 무시하는 지시어이므로 라운드트립에 안전하다.
// Decomposed -> canonical text. The version header is a parser-ignored directive, so it is round-trip safe.
export function 직렬화(분해: hierarchy.분해결과) -> string {
let mut 결과 = "version 1\n"
// 칩 정의는 평탄화 이전 원시 정규 본문(디.본문)으로 내보낸다. 평탄 내부는 중첩 칩에서 __ 네임스페이스 아이디라 재import가
// 거부하기 때문이다(비중첩 칩은 본문 == 평탄 내부라 형식 동일). / Emit each chip def from its pre-flatten canonical body
// (디.본문); flat internals carry __-namespaced ids for nested chips that re-import rejects (non-nested: 본문 == flat).
for 디 in 분해.정의들 {
결과 = 결과 + "chipdef {디.이름}\n" + 디.본문 + "endchip\n"
}
for 엔 in 분해.주노드들 { 결과 = 결과 + 노드줄(엔) + "\n" }
for 더블유 in 분해.주전선들 { 결과 = 결과 + 전선줄(더블유) + "\n" }
for 아이 in 분해.인스턴스들 { 결과 = 결과 + "chip {아이.인스} {아이.칩이름} {아이.엑스} {아이.와이}\n" }
결과
}
// ===== chips/arith.tpz =====
// 산술 칩 라이브러리(중첩 칩): 반가산기 → 전가산기 → 4비트 리플캐리 가산기. main이 `library arith`에서 앞에 붙인다.
// Arithmetic chip library (nested chips): half adder -> full adder -> 4-bit ripple-carry adder; main prepends it on `library arith`.
// 정직: 여기엔 게이트가 없다. 게이트로 조립한 넷리스트 텍스트뿐이고, 신호는 core.gate 진리표로만 계산된다(전가산기·가산기에 산술 없음).
// Honest: no gate logic here, only netlist text assembled from gates; signals are computed by core.gate truth tables only.
//
// 하우스룰(chips): export는 타입/함수/불변 최상위만. 최상위 가변·print·input·host 금지. 지역 let mut는 허용.
// House rule (chips): export only types, functions, immutable top-level bindings; local let mut is fine (pure).
//
// Key identifiers: 산술라이브러리=arithLibrary, 반가산기=halfAdder, 전가산기=fullAdder, 사비트가산기=adder4
// 반가산기: XOR(합) + AND(자리올림). / Half adder: XOR (sum) + AND (carry).
function 반가산기() -> string {
"chipdef halfadder\nnode a INPUT 0 0\nnode b INPUT 0 40\nnode x XOR 60 0\nnode g AND 60 50\nnode s OUTPUT 130 0\nnode k OUTPUT 130 50\nwire a:out x:in0\nwire b:out x:in1\nwire a:out g:in0\nwire b:out g:in1\nwire x:out s:in0\nwire g:out k:in0\nextin A a\nextin B b\nextout S s\nextout K k\nendchip\n"
}
// 전가산기: 반가산기 두 개 + OR. S = A xor B xor Cin, Cout = 다수결. / Full adder: two half adders + an OR.
function 전가산기() -> string {
"chipdef fulladder\nnode fa INPUT 0 0\nnode fb INPUT 0 40\nnode fc INPUT 0 90\nnode fs OUTPUT 260 0\nnode fco OUTPUT 260 90\nchip h1 halfadder 70 0\nchip h2 halfadder 150 40\nnode orc OR 210 90\nwire fa:out h1:A\nwire fb:out h1:B\nwire h1:S h2:A\nwire fc:out h2:B\nwire h2:S fs:in0\nwire h1:K orc:in0\nwire h2:K orc:in1\nwire orc:out fco:in0\nextin A fa\nextin B fb\nextin Cin fc\nextout S fs\nextout Cout fco\nendchip\n"
}
// 4비트 리플캐리 가산기: 전가산기 네 개를 자리올림으로 잇는다. / 4-bit ripple-carry adder: four full adders chained by carry.
function 사비트가산기() -> string {
let mut 텍 = "chipdef adder4\n"
텍 = 텍 + "node a0 INPUT 0 0\nnode a1 INPUT 0 30\nnode a2 INPUT 0 60\nnode a3 INPUT 0 90\n"
텍 = 텍 + "node b0 INPUT 0 130\nnode b1 INPUT 0 160\nnode b2 INPUT 0 190\nnode b3 INPUT 0 220\n"
텍 = 텍 + "node cin INPUT 0 270\n"
텍 = 텍 + "node s0 OUTPUT 460 0\nnode s1 OUTPUT 460 30\nnode s2 OUTPUT 460 60\nnode s3 OUTPUT 460 90\nnode cout OUTPUT 460 140\n"
텍 = 텍 + "chip f0 fulladder 150 0\nchip f1 fulladder 150 90\nchip f2 fulladder 150 180\nchip f3 fulladder 150 270\n"
텍 = 텍 + "wire a0:out f0:A\nwire b0:out f0:B\nwire cin:out f0:Cin\nwire f0:S s0:in0\nwire f0:Cout f1:Cin\n"
텍 = 텍 + "wire a1:out f1:A\nwire b1:out f1:B\nwire f1:S s1:in0\nwire f1:Cout f2:Cin\n"
텍 = 텍 + "wire a2:out f2:A\nwire b2:out f2:B\nwire f2:S s2:in0\nwire f2:Cout f3:Cin\n"
텍 = 텍 + "wire a3:out f3:A\nwire b3:out f3:B\nwire f3:S s3:in0\nwire f3:Cout cout:in0\n"
텍 = 텍 + "extin a0 a0\nextin a1 a1\nextin a2 a2\nextin a3 a3\n"
텍 = 텍 + "extin b0 b0\nextin b1 b1\nextin b2 b2\nextin b3 b3\nextin cin cin\n"
텍 = 텍 + "extout s0 s0\nextout s1 s1\nextout s2 s2\nextout s3 s3\nextout cout cout\n"
텍 = 텍 + "endchip\n"
텍
}
// 라이브러리 전체 텍스트(정의 순서: 반가산기 → 전가산기 → 4비트 가산기). / The whole library text in definition order.
export function 산술라이브러리() -> string {
반가산기() + 전가산기() + 사비트가산기()
}
// ===== chips/alu.tpz =====
// 산술논리장치(ALU) 칩 라이브러리(중첩 칩): 2:1 먹스 + 4비트 ALU. main이 `library alu`에서 앞에 붙인다.
// ALU chip library (nested chips): a 2-to-1 mux + a 4-bit ALU; main prepends it on `library alu`.
// 정직: 여기엔 게이트가 없다. 게이트로 조립한 넷리스트 텍스트뿐이고, 신호는 core.gate 진리표로만 계산된다.
// 뺄셈도 게이트로만 한다: B를 mode로 XOR(비트반전) + 자리올림 입력=mode = 2의 보수. 산술 연산자 없음.
// Honest: no gate logic here, only netlist text. Subtraction is gates only: B XOR mode (bit-invert) + carry-in = mode
// = two's complement. No arithmetic operators on the signal path.
//
// 하우스룰(chips): export는 타입/함수/불변 최상위만. 최상위 가변·print·input·host 금지. 지역 let mut는 허용.
// House rule (chips): export only types, functions, immutable top-level bindings; local let mut is fine (pure).
//
// alu4 인터페이스: extin a0..a3 b0..b3 op0 op1 (op1 op0: 00 ADD, 01 SUB, 10 AND, 11 OR); extout y0..y3 cout zero.
// ADD/SUB는 하나의 adder4를 공유한다: mode = AND(NOT op1, op0)이 SUB(01)에서만 1 → B 비트반전 + cin=1.
// 먹스 두 단: lm = mux2(AND, OR, op0), y = mux2(arith, lm, op1). zero = NOR(y0..y3), cout = adder의 자리올림.
//
// Key identifiers: 멀티플렉서2=mux2, 산술논리장치=alu4, ALU라이브러리=aluLibrary
import chips.arith { 산술라이브러리 }
// 2:1 먹스(1비트): O = (D0 AND NOT S) OR (D1 AND S). S=0이면 D0, S=1이면 D1. / 1-bit 2-to-1 mux.
function 멀티플렉서2() -> string {
let mut 텍 = "chipdef mux2\n"
텍 = 텍 + "node d0 INPUT 0 0\nnode d1 INPUT 0 40\nnode s INPUT 0 80\n"
텍 = 텍 + "node ns NOT 70 80\nnode t0 AND 140 10\nnode t1 AND 140 60\nnode o OR 220 35\n"
텍 = 텍 + "node out OUTPUT 300 35\n"
텍 = 텍 + "wire s:out ns:in0\nwire d0:out t0:in0\nwire ns:out t0:in1\n"
텍 = 텍 + "wire d1:out t1:in0\nwire s:out t1:in1\nwire t0:out o:in0\nwire t1:out o:in1\nwire o:out out:in0\n"
텍 = 텍 + "extin D0 d0\nextin D1 d1\nextin S s\nextout O out\nendchip\n"
텍
}
// 4비트 ALU: 하나의 adder4(ADD/SUB) + 비트별 AND/OR + 두 단 먹스. 플래그 cout/zero. / 4-bit ALU.
function 산술논리장치() -> string {
let mut 텍 = "chipdef alu4\n"
// 입력 / inputs
텍 = 텍 + "node a0 INPUT 0 0\nnode a1 INPUT 0 30\nnode a2 INPUT 0 60\nnode a3 INPUT 0 90\n"
텍 = 텍 + "node b0 INPUT 0 140\nnode b1 INPUT 0 170\nnode b2 INPUT 0 200\nnode b3 INPUT 0 230\n"
텍 = 텍 + "node op0 INPUT 0 280\nnode op1 INPUT 0 310\n"
// mode = AND(NOT op1, op0) — SUB(01)에서만 1 / high only for SUB
텍 = 텍 + "node nop1 NOT 80 310\nnode mode AND 150 295\n"
// B 유효값 = XOR(b_i, mode) / B effective = B xor mode (invert on SUB)
텍 = 텍 + "node be0 XOR 230 140\nnode be1 XOR 230 170\nnode be2 XOR 230 200\nnode be3 XOR 230 230\n"
// 공유 가산기 / shared adder
텍 = 텍 + "chip ad adder4 330 80\n"
// 비트별 AND/OR / bitwise AND, OR
텍 = 텍 + "node an0 AND 230 0\nnode an1 AND 230 30\nnode an2 AND 230 60\nnode an3 AND 230 90\n"
텍 = 텍 + "node or0 OR 290 0\nnode or1 OR 290 30\nnode or2 OR 290 60\nnode or3 OR 290 90\n"
// 논리 먹스: lm = mux2(AND, OR, op0) / logic mux per bit
텍 = 텍 + "chip lm0 mux2 380 0\nchip lm1 mux2 380 30\nchip lm2 mux2 380 60\nchip lm3 mux2 380 90\n"
// 최종 먹스: y = mux2(arith, lm, op1) / final mux selects arith vs logic
텍 = 텍 + "chip fm0 mux2 500 0\nchip fm1 mux2 500 30\nchip fm2 mux2 500 60\nchip fm3 mux2 500 90\n"
// 출력 / outputs
텍 = 텍 + "node y0 OUTPUT 640 0\nnode y1 OUTPUT 640 30\nnode y2 OUTPUT 640 60\nnode y3 OUTPUT 640 90\nnode cout OUTPUT 640 140\n"
// zero = NOR(y0..y3) = NOT(OR4) / zero flag
텍 = 텍 + "node zo01 OR 560 200\nnode zo23 OR 560 240\nnode zoa OR 620 220\nnode znz NOT 680 220\nnode zero OUTPUT 740 220\n"
// mode 배선 / mode wiring
텍 = 텍 + "wire op1:out nop1:in0\nwire nop1:out mode:in0\nwire op0:out mode:in1\n"
// B 유효값 배선 / B effective wiring
텍 = 텍 + "wire b0:out be0:in0\nwire mode:out be0:in1\nwire b1:out be1:in0\nwire mode:out be1:in1\n"
텍 = 텍 + "wire b2:out be2:in0\nwire mode:out be2:in1\nwire b3:out be3:in0\nwire mode:out be3:in1\n"
// 가산기 입력 / adder inputs
텍 = 텍 + "wire a0:out ad:a0\nwire a1:out ad:a1\nwire a2:out ad:a2\nwire a3:out ad:a3\n"
텍 = 텍 + "wire be0:out ad:b0\nwire be1:out ad:b1\nwire be2:out ad:b2\nwire be3:out ad:b3\nwire mode:out ad:cin\n"
// 비트별 AND/OR 배선 / bitwise wiring
텍 = 텍 + "wire a0:out an0:in0\nwire b0:out an0:in1\nwire a1:out an1:in0\nwire b1:out an1:in1\n"
텍 = 텍 + "wire a2:out an2:in0\nwire b2:out an2:in1\nwire a3:out an3:in0\nwire b3:out an3:in1\n"
텍 = 텍 + "wire a0:out or0:in0\nwire b0:out or0:in1\nwire a1:out or1:in0\nwire b1:out or1:in1\n"
텍 = 텍 + "wire a2:out or2:in0\nwire b2:out or2:in1\nwire a3:out or3:in0\nwire b3:out or3:in1\n"
// 논리 먹스 배선 / logic mux wiring
텍 = 텍 + "wire an0:out lm0:D0\nwire or0:out lm0:D1\nwire op0:out lm0:S\n"
텍 = 텍 + "wire an1:out lm1:D0\nwire or1:out lm1:D1\nwire op0:out lm1:S\n"
텍 = 텍 + "wire an2:out lm2:D0\nwire or2:out lm2:D1\nwire op0:out lm2:S\n"
텍 = 텍 + "wire an3:out lm3:D0\nwire or3:out lm3:D1\nwire op0:out lm3:S\n"
// 최종 먹스 배선 / final mux wiring
텍 = 텍 + "wire ad:s0 fm0:D0\nwire lm0:O fm0:D1\nwire op1:out fm0:S\n"
텍 = 텍 + "wire ad:s1 fm1:D0\nwire lm1:O fm1:D1\nwire op1:out fm1:S\n"
텍 = 텍 + "wire ad:s2 fm2:D0\nwire lm2:O fm2:D1\nwire op1:out fm2:S\n"
텍 = 텍 + "wire ad:s3 fm3:D0\nwire lm3:O fm3:D1\nwire op1:out fm3:S\n"
// 출력 배선 / output wiring
텍 = 텍 + "wire fm0:O y0:in0\nwire fm1:O y1:in0\nwire fm2:O y2:in0\nwire fm3:O y3:in0\nwire ad:cout cout:in0\n"
// zero 배선 / zero wiring
텍 = 텍 + "wire fm0:O zo01:in0\nwire fm1:O zo01:in1\nwire fm2:O zo23:in0\nwire fm3:O zo23:in1\n"
텍 = 텍 + "wire zo01:out zoa:in0\nwire zo23:out zoa:in1\nwire zoa:out znz:in0\nwire znz:out zero:in0\n"
// 외부 핀 / external pins
텍 = 텍 + "extin a0 a0\nextin a1 a1\nextin a2 a2\nextin a3 a3\n"
텍 = 텍 + "extin b0 b0\nextin b1 b1\nextin b2 b2\nextin b3 b3\nextin op0 op0\nextin op1 op1\n"
텍 = 텍 + "extout y0 y0\nextout y1 y1\nextout y2 y2\nextout y3 y3\nextout cout cout\nextout zero zero\n"
텍 = 텍 + "endchip\n"
텍
}
// 라이브러리 전체(정의 순서: 산술 라이브러리 → mux2 → alu4). alu4는 adder4와 mux2를 쓰므로 뒤에 온다.
// The whole library in definition order; alu4 depends on adder4 and mux2, so it comes last.
export function ALU라이브러리() -> string {
산술라이브러리() + 멀티플렉서2() + 산술논리장치()
}
// ===== chips/seq.tpz =====
// 순차 칩 라이브러리(되먹임): SR 래치(교차결합 NAND). main이 `library seq`에서 앞에 붙인다.
// Sequential chip library (feedback): an SR latch (cross-coupled NAND); main prepends it on `library seq`.
// 정직: 여기엔 게이트가 없다. 게이트로 조립한 넷리스트 텍스트뿐이고, 되먹임은 엔진(정착평가회로)이 정착시킨다.
// Honest: no gate logic here, only netlist text assembled from NAND gates; the feedback is settled by the engine.
//
// 하우스룰(chips): export는 타입/함수/불변 최상위만. 최상위 가변·print·input·host 금지. 지역 let mut는 허용.
// House rule (chips): export only types, functions, immutable top-level bindings; a local let mut is fine (pure).
//
// Key identifiers: 순차라이브러리=seqLibrary, 에스알래치=srLatch, 디래치=dLatch, 디플립플롭=dFlipFlop
// SR 래치: 교차결합 NAND 두 개. Q=NAND(S̄,Q̄), Q̄=NAND(R̄,Q). 입력은 활성-로우(nS=0이면 셋, nR=0이면 리셋,
// 둘 다 1이면 직전 상태 유지). 유지는 되먹임 루프라 조합 위상정렬로는 못 풀고 엔진의 unit-delay 정착이 푼다.
// SR latch: two cross-coupled NANDs. Active-low (nS=0 sets, nR=0 resets, both 1 holds the prior state).
// Hold is a feedback loop, so the combinational topological pass cannot resolve it; the engine's unit-delay settle does.
function 에스알래치() -> string {
"chipdef srlatch\nnode si INPUT 0 0\nnode ri INPUT 0 80\nnode q NAND 90 10\nnode nq NAND 90 70\nnode qo OUTPUT 180 10\nnode nqo OUTPUT 180 70\nwire si:out q:in0\nwire nq:out q:in1\nwire ri:out nq:in0\nwire q:out nq:in1\nwire q:out qo:in0\nwire nq:out nqo:in0\nextin nS si\nextin nR ri\nextout Q qo\nextout Qb nqo\nendchip\n"
}
// 게이트드 D 래치: E=1이면 Q가 D를 따르고(투명), E=0이면 직전 Q를 유지한다. S̄=NAND(D,E), R̄=NAND(¬D,E)로
// SR 래치(하위 칩)를 구동한다. ¬D=NAND(D,D). 되먹임은 srlatch 안에 있으므로 엔진이 정착시킨다.
// Gated D latch: transparent when E=1 (Q follows D), holds when E=0. Drives the srlatch sub-chip via
// S̄=NAND(D,E), R̄=NAND(¬D,E), ¬D=NAND(D,D). The feedback lives inside srlatch; the engine settles it.
function 디래치() -> string {
"chipdef dlatch\nnode di INPUT 0 20\nnode ei INPUT 0 120\nnode nd NAND 90 30\nnode sb NAND 90 80\nnode rb NAND 180 110\nchip lat srlatch 270 60\nnode qo OUTPUT 380 40\nnode nqo OUTPUT 380 100\nwire di:out nd:in0\nwire di:out nd:in1\nwire di:out sb:in0\nwire ei:out sb:in1\nwire nd:out rb:in0\nwire ei:out rb:in1\nwire sb:out lat:nS\nwire rb:out lat:nR\nwire lat:Q qo:in0\nwire lat:Qb nqo:in0\nextin D di\nextin E ei\nextout Q qo\nextout Qb nqo\nendchip\n"
}
// 마스터-슬레이브 D 플립플롭: 게이트드 D 래치 둘(마스터 E=¬CLK, 슬레이브 E=CLK, 마스터 Q→슬레이브 D).
// CLK 상승에지에서 D를 포획하고 그 외엔 유지하는 에지트리거. 래치 칩을 품은 되먹임 칩(중첩 순차 투영 대상).
// Master-slave D flip-flop: two gated D latches (master E=¬CLK, slave E=CLK, master Q -> slave D).
// Edge-triggered: captures D on the CLK rising edge, holds otherwise. A feedback chip that holds latch sub-chips.
function 디플립플롭() -> string {
"chipdef dff\nnode di INPUT 0 20\nnode ci INPUT 0 140\nnode nc NAND 90 150\nchip m dlatch 180 20\nchip s dlatch 320 20\nnode qo OUTPUT 440 40\nnode nqo OUTPUT 440 100\nwire ci:out nc:in0\nwire ci:out nc:in1\nwire di:out m:D\nwire nc:out m:E\nwire m:Q s:D\nwire ci:out s:E\nwire s:Q qo:in0\nwire s:Qb nqo:in0\nextin D di\nextin CLK ci\nextout Q qo\nextout Qb nqo\nendchip\n"
}
// 리셋 가능 SR 래치: 일반 SR 래치 + 비동기 리셋(RST=1이면 클럭 없이 즉시 Q=0). 리셋을 게이트로만 만든다.
// RST=1일 때 nS_eff=OR(nS,RST)=1로 셋을 막고 R̄_eff=AND(nR,¬RST)=0으로 리셋을 걸어 Q→0이 일의적으로 정착한다
// (피드백 모호성이 없어 콜드스타트에서도 발진하지 않고 0으로 수렴). RST=0이면 평범한 SR 래치로 동작한다.
// Resettable SR latch: a normal SR latch plus an asynchronous reset (RST=1 forces Q=0 immediately, no clock),
// built only from NAND. With RST=1, nS_eff=OR(nS,RST)=1 blocks set and R̄_eff=AND(nR,¬RST)=0 asserts reset, so
// Q settles to 0 unambiguously (no feedback ambiguity, so even a cold start converges to 0). RST=0 -> plain SR latch.
function 리셋에스알래치() -> string {
"chipdef srlatchr\nnode si INPUT 0 0\nnode ri INPUT 0 60\nnode rsti INPUT 0 120\nnode notrst NAND 80 120\nnode nns NAND 80 0\nnode nseff NAND 160 20\nnode rt NAND 160 80\nnode reff NAND 240 80\nnode q NAND 320 20\nnode nq NAND 320 90\nnode qo OUTPUT 410 20\nnode nqo OUTPUT 410 90\nwire rsti:out notrst:in0\nwire rsti:out notrst:in1\nwire si:out nns:in0\nwire si:out nns:in1\nwire nns:out nseff:in0\nwire notrst:out nseff:in1\nwire ri:out rt:in0\nwire notrst:out rt:in1\nwire rt:out reff:in0\nwire rt:out reff:in1\nwire nseff:out q:in0\nwire nq:out q:in1\nwire reff:out nq:in0\nwire q:out nq:in1\nwire q:out qo:in0\nwire nq:out nqo:in0\nextin nS si\nextin nR ri\nextin RST rsti\nextout Q qo\nextout Qb nqo\nendchip\n"
}
// 리셋 가능 게이트드 D 래치: 디래치와 같되 리셋 SR 래치를 쓰고 RST를 그대로 전달한다.
// Resettable gated D latch: like dlatch but uses the resettable SR latch and passes RST through.
function 리셋디래치() -> string {
"chipdef dlatchr\nnode di INPUT 0 0\nnode ei INPUT 0 80\nnode rsti INPUT 0 160\nnode nd NAND 80 0\nnode sb NAND 80 60\nnode rb NAND 160 100\nchip lat srlatchr 250 70\nnode qo OUTPUT 380 40\nnode nqo OUTPUT 380 110\nwire di:out nd:in0\nwire di:out nd:in1\nwire di:out sb:in0\nwire ei:out sb:in1\nwire nd:out rb:in0\nwire ei:out rb:in1\nwire sb:out lat:nS\nwire rb:out lat:nR\nwire rsti:out lat:RST\nwire lat:Q qo:in0\nwire lat:Qb nqo:in0\nextin D di\nextin E ei\nextin RST rsti\nextout Q qo\nextout Qb nqo\nendchip\n"
}
// 리셋 가능 D 플립플롭: 디플립플롭과 같되 리셋 D 래치 둘을 쓰고 RST를 마스터·슬레이브 모두에 전달한다.
// RST=1이면 두 래치가 0으로 정착해 출력 Q=0이 클럭 없이 정의된다(콜드스타트 발진 해소). 이후 RST=0이면 다음
// CLK 상승에지까지 0을 유지한다. Resettable D flip-flop: like dff but two resettable D latches, RST to both
// master and slave. RST=1 -> both latches settle to 0 so Q=0 is defined with no clock (fixes cold-start
// oscillation); after RST=0 it holds 0 until the next CLK rising edge.
function 리셋디플립플롭() -> string {
"chipdef dffr\nnode di INPUT 0 0\nnode ci INPUT 0 100\nnode rsti INPUT 0 200\nnode nc NAND 90 100\nchip m dlatchr 180 0\nchip s dlatchr 330 0\nnode qo OUTPUT 460 40\nnode nqo OUTPUT 460 110\nwire ci:out nc:in0\nwire ci:out nc:in1\nwire di:out m:D\nwire nc:out m:E\nwire rsti:out m:RST\nwire m:Q s:D\nwire ci:out s:E\nwire rsti:out s:RST\nwire s:Q qo:in0\nwire s:Qb nqo:in0\nextin D di\nextin CLK ci\nextin RST rsti\nextout Q qo\nextout Qb nqo\nendchip\n"
}
// 4비트 레지스터: 리셋 D 플립플롭 넷이 CLK·RST를 공유한다. RST로 0000 초기화, CLK 상승에지에 D0..D3를 포획해 유지.
// 4-bit register: four resettable D flip-flops sharing CLK and RST. RST clears to 0000; a CLK rising edge captures D0..D3 and holds.
function 레지스터() -> string {
"chipdef reg4\nnode d0 INPUT 0 0\nnode d1 INPUT 0 60\nnode d2 INPUT 0 120\nnode d3 INPUT 0 180\nnode clk INPUT 0 240\nnode rst INPUT 0 300\nchip f0 dffr 120 0\nchip f1 dffr 120 80\nchip f2 dffr 120 160\nchip f3 dffr 120 240\nnode q0 OUTPUT 320 0\nnode q1 OUTPUT 320 80\nnode q2 OUTPUT 320 160\nnode q3 OUTPUT 320 240\nwire d0:out f0:D\nwire clk:out f0:CLK\nwire rst:out f0:RST\nwire f0:Q q0:in0\nwire d1:out f1:D\nwire clk:out f1:CLK\nwire rst:out f1:RST\nwire f1:Q q1:in0\nwire d2:out f2:D\nwire clk:out f2:CLK\nwire rst:out f2:RST\nwire f2:Q q2:in0\nwire d3:out f3:D\nwire clk:out f3:CLK\nwire rst:out f3:RST\nwire f3:Q q3:in0\nextin D0 d0\nextin D1 d1\nextin D2 d2\nextin D3 d3\nextin CLK clk\nextin RST rst\nextout Q0 q0\nextout Q1 q1\nextout Q2 q2\nextout Q3 q3\nendchip\n"
}
// 4비트 증분기(+1): 캐리인 1을 구조로 박는다 — 비트0=NOT a0, 캐리1=a0; 비트i = a_i XOR 캐리_i, 캐리_{i+1}=a_i AND 캐리_i.
// 상수 입력이 필요 없다(+1이 구조에 들어있다). 조합 회로(되먹임 없음)라 위상정렬로 풀린다.
// 4-bit incrementer (+1): carry-in 1 baked into the structure — bit0=NOT a0, carry1=a0; bit_i = a_i XOR carry_i,
// carry_{i+1} = a_i AND carry_i. No constant source needed (the +1 is structural). Combinational (no feedback).
function 증분기() -> string {
"chipdef inc4\nnode ia0 INPUT 0 0\nnode ia1 INPUT 0 60\nnode ia2 INPUT 0 120\nnode ia3 INPUT 0 180\nnode s0g NOT 90 0\nnode x1 XOR 110 60\nnode c1 AND 110 100\nnode x2 XOR 190 120\nnode c2 AND 190 160\nnode x3 XOR 270 180\nnode coutg AND 270 220\nnode os0 OUTPUT 360 0\nnode os1 OUTPUT 360 60\nnode os2 OUTPUT 360 120\nnode os3 OUTPUT 360 180\nnode ocout OUTPUT 360 240\nwire ia0:out s0g:in0\nwire s0g:out os0:in0\nwire ia1:out x1:in0\nwire ia0:out x1:in1\nwire x1:out os1:in0\nwire ia1:out c1:in0\nwire ia0:out c1:in1\nwire ia2:out x2:in0\nwire c1:out x2:in1\nwire x2:out os2:in0\nwire ia2:out c2:in0\nwire c1:out c2:in1\nwire ia3:out x3:in0\nwire c2:out x3:in1\nwire x3:out os3:in0\nwire ia3:out coutg:in0\nwire c2:out coutg:in1\nwire coutg:out ocout:in0\nextin a0 ia0\nextin a1 ia1\nextin a2 ia2\nextin a3 ia3\nextout s0 os0\nextout s1 os1\nextout s2 os2\nextout s3 os3\nextout cout ocout\nendchip\n"
}
// 4비트 카운터: 레지스터의 Q를 증분기에 먹이고 그 합을 레지스터의 D로 되돌린다(CLK·RST 공유). 루프 Q→inc→D는
// 레지스터(FF)에서 끊기므로 조합부(inc4)는 비순환이고, FF 되먹임만 엔진이 정착시킨다. RST→0000, CLK 상승에지마다 Q←Q+1.
// 4-bit counter: the register's Q feeds the incrementer whose sum feeds the register's D (sharing CLK and RST).
// The loop Q->inc->D is broken at the register (the FFs hold state), so the combinational part (inc4) is acyclic
// and only the FF feedback is settled by the engine. RST clears to 0000; each CLK rising edge does Q <- Q+1.
function 카운터() -> string {
"chipdef counter4\nnode ci INPUT 0 0\nnode ri INPUT 0 60\nchip r reg4 120 0\nchip i inc4 360 0\nnode oq0 OUTPUT 560 0\nnode oq1 OUTPUT 560 60\nnode oq2 OUTPUT 560 120\nnode oq3 OUTPUT 560 180\nwire ci:out r:CLK\nwire ri:out r:RST\nwire r:Q0 i:a0\nwire r:Q1 i:a1\nwire r:Q2 i:a2\nwire r:Q3 i:a3\nwire i:s0 r:D0\nwire i:s1 r:D1\nwire i:s2 r:D2\nwire i:s3 r:D3\nwire r:Q0 oq0:in0\nwire r:Q1 oq1:in0\nwire r:Q2 oq2:in0\nwire r:Q3 oq3:in0\nextin CLK ci\nextin RST ri\nextout Q0 oq0\nextout Q1 oq1\nextout Q2 oq2\nextout Q3 oq3\nendchip\n"
}
// 2-to-4 주소 디코더: 주소 두 비트(a0=LSB, a1)를 원-핫 4선으로. s0=¬a0·¬a1, s1=a0·¬a1, s2=¬a0·a1, s3=a0·a1.
// ¬는 NOT 게이트, 곱은 AND 게이트(둘 다 core.gate=NAND 합성). 조합 회로(되먹임 없음).
// 2-to-4 address decoder: two address bits (a0=LSB, a1) -> a one-hot 4-line select. AND of the bits and their NOTs.
function 디코더() -> string {
"chipdef decoder24\nnode da0 INPUT 0 0\nnode da1 INPUT 0 80\nnode dn0 NOT 80 0\nnode dn1 NOT 80 80\nnode ds0 AND 170 0\nnode ds1 AND 170 60\nnode ds2 AND 170 120\nnode ds3 AND 170 180\nnode do0 OUTPUT 260 0\nnode do1 OUTPUT 260 60\nnode do2 OUTPUT 260 120\nnode do3 OUTPUT 260 180\nwire da0:out dn0:in0\nwire da1:out dn1:in0\nwire dn0:out ds0:in0\nwire dn1:out ds0:in1\nwire da0:out ds1:in0\nwire dn1:out ds1:in1\nwire dn0:out ds2:in0\nwire da1:out ds2:in1\nwire da0:out ds3:in0\nwire da1:out ds3:in1\nwire ds0:out do0:in0\nwire ds1:out do1:in0\nwire ds2:out do2:in0\nwire ds3:out do3:in0\nextin a0 da0\nextin a1 da1\nextout s0 do0\nextout s1 do1\nextout s2 do2\nextout s3 do3\nendchip\n"
}
// 로드-이네이블 4비트 레지스터: LD=1이면 CLK 상승에지에 D를 포획, LD=0이면 유지. 비트별 먹스 D_eff=LD?D:Q로
// reg4(하위 칩)의 D를 만든다(¬LD=NOT, 게이트로만). 되먹임 Q→먹스→D는 reg4의 FF에서 끊겨 엔진이 정착시킨다.
// Load-enable 4-bit register: LD=1 captures D on the CLK rising edge, LD=0 holds. A per-bit mux D_eff=LD?D:Q feeds
// the reg4 sub-chip (¬LD via NOT, all gates). The Q->mux->D feedback breaks at reg4's FFs; the engine settles it.
function 로드레지스터() -> string {
"chipdef reg4le\nnode ed0 INPUT 0 0\nnode ed1 INPUT 0 40\nnode ed2 INPUT 0 80\nnode ed3 INPUT 0 120\nnode eld INPUT 0 160\nnode eclk INPUT 0 200\nnode erst INPUT 0 240\nnode enl NOT 70 160\nnode ea0 AND 130 0\nnode eb0 AND 130 20\nnode em0 OR 200 0\nnode ea1 AND 130 40\nnode eb1 AND 130 60\nnode em1 OR 200 40\nnode ea2 AND 130 80\nnode eb2 AND 130 100\nnode em2 OR 200 80\nnode ea3 AND 130 120\nnode eb3 AND 130 140\nnode em3 OR 200 120\nchip r reg4 280 0\nnode eq0 OUTPUT 460 0\nnode eq1 OUTPUT 460 40\nnode eq2 OUTPUT 460 80\nnode eq3 OUTPUT 460 120\nwire eld:out enl:in0\nwire eld:out ea0:in0\nwire ed0:out ea0:in1\nwire enl:out eb0:in0\nwire r:Q0 eb0:in1\nwire ea0:out em0:in0\nwire eb0:out em0:in1\nwire em0:out r:D0\nwire eld:out ea1:in0\nwire ed1:out ea1:in1\nwire enl:out eb1:in0\nwire r:Q1 eb1:in1\nwire ea1:out em1:in0\nwire eb1:out em1:in1\nwire em1:out r:D1\nwire eld:out ea2:in0\nwire ed2:out ea2:in1\nwire enl:out eb2:in0\nwire r:Q2 eb2:in1\nwire ea2:out em2:in0\nwire eb2:out em2:in1\nwire em2:out r:D2\nwire eld:out ea3:in0\nwire ed3:out ea3:in1\nwire enl:out eb3:in0\nwire r:Q3 eb3:in1\nwire ea3:out em3:in0\nwire eb3:out em3:in1\nwire em3:out r:D3\nwire eclk:out r:CLK\nwire erst:out r:RST\nwire r:Q0 eq0:in0\nwire r:Q1 eq1:in0\nwire r:Q2 eq2:in0\nwire r:Q3 eq3:in0\nextin D0 ed0\nextin D1 ed1\nextin D2 ed2\nextin D3 ed3\nextin LD eld\nextin CLK eclk\nextin RST erst\nextout Q0 eq0\nextout Q1 eq1\nextout Q2 eq2\nextout Q3 eq3\nendchip\n"
}
// 4워드 x 4비트 RAM(레지스터 파일): 디코더가 주소를 원-핫으로 풀고, 워드별 로드=AND(sel,WE)로 그 워드만 CLK
// 상승에지에 데이터버스를 포획(나머지는 유지). 읽기는 조합 4-to-1 먹스(원-핫 sel로 워드의 Q를 고름). RST는 전 워드 0.
// 4-word x 4-bit RAM (register file): the decoder one-hots the address; per-word load=AND(sel,WE) captures the data
// bus on the CLK rising edge only for the selected word (others hold). Read is a combinational 4-to-1 mux (the one-hot
// sel selects a word's Q). RST clears all words. ram4 -> decoder + 4 reg4le + read mux -> reg4 -> dffr -> ... -> NAND.
function 램() -> string {
"chipdef ram4\nnode ma0 INPUT 0 0\nnode ma1 INPUT 0 40\nnode md0 INPUT 0 80\nnode md1 INPUT 0 120\nnode md2 INPUT 0 160\nnode md3 INPUT 0 200\nnode mwe INPUT 0 240\nnode mclk INPUT 0 280\nnode mrst INPUT 0 320\nchip dec decoder24 130 0\nnode ml0 AND 280 0\nnode ml1 AND 280 60\nnode ml2 AND 280 120\nnode ml3 AND 280 180\nchip w0 reg4le 400 0\nchip w1 reg4le 400 200\nchip w2 reg4le 400 400\nchip w3 reg4le 400 600\nnode m00 AND 700 0\nnode m01 AND 700 30\nnode m02 AND 700 60\nnode m03 AND 700 90\nnode m04 OR 790 0\nnode m05 OR 790 60\nnode m06 OR 870 0\nnode m10 AND 700 150\nnode m11 AND 700 180\nnode m12 AND 700 210\nnode m13 AND 700 240\nnode m14 OR 790 150\nnode m15 OR 790 210\nnode m16 OR 870 150\nnode m20 AND 700 300\nnode m21 AND 700 330\nnode m22 AND 700 360\nnode m23 AND 700 390\nnode m24 OR 790 300\nnode m25 OR 790 360\nnode m26 OR 870 300\nnode m30 AND 700 450\nnode m31 AND 700 480\nnode m32 AND 700 510\nnode m33 AND 700 540\nnode m34 OR 790 450\nnode m35 OR 790 510\nnode m36 OR 870 450\nnode mq0 OUTPUT 950 0\nnode mq1 OUTPUT 950 150\nnode mq2 OUTPUT 950 300\nnode mq3 OUTPUT 950 450\nwire ma0:out dec:a0\nwire ma1:out dec:a1\nwire dec:s0 ml0:in0\nwire mwe:out ml0:in1\nwire dec:s1 ml1:in0\nwire mwe:out ml1:in1\nwire dec:s2 ml2:in0\nwire mwe:out ml2:in1\nwire dec:s3 ml3:in0\nwire mwe:out ml3:in1\nwire md0:out w0:D0\nwire md1:out w0:D1\nwire md2:out w0:D2\nwire md3:out w0:D3\nwire ml0:out w0:LD\nwire mclk:out w0:CLK\nwire mrst:out w0:RST\nwire md0:out w1:D0\nwire md1:out w1:D1\nwire md2:out w1:D2\nwire md3:out w1:D3\nwire ml1:out w1:LD\nwire mclk:out w1:CLK\nwire mrst:out w1:RST\nwire md0:out w2:D0\nwire md1:out w2:D1\nwire md2:out w2:D2\nwire md3:out w2:D3\nwire ml2:out w2:LD\nwire mclk:out w2:CLK\nwire mrst:out w2:RST\nwire md0:out w3:D0\nwire md1:out w3:D1\nwire md2:out w3:D2\nwire md3:out w3:D3\nwire ml3:out w3:LD\nwire mclk:out w3:CLK\nwire mrst:out w3:RST\nwire dec:s0 m00:in0\nwire w0:Q0 m00:in1\nwire dec:s1 m01:in0\nwire w1:Q0 m01:in1\nwire dec:s2 m02:in0\nwire w2:Q0 m02:in1\nwire dec:s3 m03:in0\nwire w3:Q0 m03:in1\nwire m00:out m04:in0\nwire m01:out m04:in1\nwire m02:out m05:in0\nwire m03:out m05:in1\nwire m04:out m06:in0\nwire m05:out m06:in1\nwire m06:out mq0:in0\nwire dec:s0 m10:in0\nwire w0:Q1 m10:in1\nwire dec:s1 m11:in0\nwire w1:Q1 m11:in1\nwire dec:s2 m12:in0\nwire w2:Q1 m12:in1\nwire dec:s3 m13:in0\nwire w3:Q1 m13:in1\nwire m10:out m14:in0\nwire m11:out m14:in1\nwire m12:out m15:in0\nwire m13:out m15:in1\nwire m14:out m16:in0\nwire m15:out m16:in1\nwire m16:out mq1:in0\nwire dec:s0 m20:in0\nwire w0:Q2 m20:in1\nwire dec:s1 m21:in0\nwire w1:Q2 m21:in1\nwire dec:s2 m22:in0\nwire w2:Q2 m22:in1\nwire dec:s3 m23:in0\nwire w3:Q2 m23:in1\nwire m20:out m24:in0\nwire m21:out m24:in1\nwire m22:out m25:in0\nwire m23:out m25:in1\nwire m24:out m26:in0\nwire m25:out m26:in1\nwire m26:out mq2:in0\nwire dec:s0 m30:in0\nwire w0:Q3 m30:in1\nwire dec:s1 m31:in0\nwire w1:Q3 m31:in1\nwire dec:s2 m32:in0\nwire w2:Q3 m32:in1\nwire dec:s3 m33:in0\nwire w3:Q3 m33:in1\nwire m30:out m34:in0\nwire m31:out m34:in1\nwire m32:out m35:in0\nwire m33:out m35:in1\nwire m34:out m36:in0\nwire m35:out m36:in1\nwire m36:out mq3:in0\nextin a0 ma0\nextin a1 ma1\nextin D0 md0\nextin D1 md1\nextin D2 md2\nextin D3 md3\nextin WE mwe\nextin CLK mclk\nextin RST mrst\nextout q0 mq0\nextout q1 mq1\nextout q2 mq2\nextout q3 mq3\nendchip\n"
}
// 3-to-8 주소 디코더: 세 비트(p0=LSB)를 원-핫 8선으로. s_j = 비트별 (p 또는 ¬p)의 3입력 AND(2단 AND). 조합.
// 3-to-8 address decoder: three bits (p0=LSB) -> one-hot 8 lines; each s_j is a 3-input AND (two 2-input ANDs) of the per-bit (p or NOT p). All gates.
function 디코더38() -> string {
"chipdef decoder38\nnode dp0 INPUT 0 0\nnode dp1 INPUT 0 60\nnode dp2 INPUT 0 120\nnode dn0 NOT 60 0\nnode dn1 NOT 60 60\nnode dn2 NOT 60 120\nnode de0 AND 130 0\nnode ds0 AND 200 0\nnode de1 AND 130 40\nnode ds1 AND 200 40\nnode de2 AND 130 80\nnode ds2 AND 200 80\nnode de3 AND 130 120\nnode ds3 AND 200 120\nnode de4 AND 130 160\nnode ds4 AND 200 160\nnode de5 AND 130 200\nnode ds5 AND 200 200\nnode de6 AND 130 240\nnode ds6 AND 200 240\nnode de7 AND 130 280\nnode ds7 AND 200 280\nnode do0 OUTPUT 280 0\nnode do1 OUTPUT 280 40\nnode do2 OUTPUT 280 80\nnode do3 OUTPUT 280 120\nnode do4 OUTPUT 280 160\nnode do5 OUTPUT 280 200\nnode do6 OUTPUT 280 240\nnode do7 OUTPUT 280 280\nwire dp0:out dn0:in0\nwire dp1:out dn1:in0\nwire dp2:out dn2:in0\nwire dn0:out de0:in0\nwire dn1:out de0:in1\nwire de0:out ds0:in0\nwire dn2:out ds0:in1\nwire dp0:out de1:in0\nwire dn1:out de1:in1\nwire de1:out ds1:in0\nwire dn2:out ds1:in1\nwire dn0:out de2:in0\nwire dp1:out de2:in1\nwire de2:out ds2:in0\nwire dn2:out ds2:in1\nwire dp0:out de3:in0\nwire dp1:out de3:in1\nwire de3:out ds3:in0\nwire dn2:out ds3:in1\nwire dn0:out de4:in0\nwire dn1:out de4:in1\nwire de4:out ds4:in0\nwire dp2:out ds4:in1\nwire dp0:out de5:in0\nwire dn1:out de5:in1\nwire de5:out ds5:in0\nwire dp2:out ds5:in1\nwire dn0:out de6:in0\nwire dp1:out de6:in1\nwire de6:out ds6:in0\nwire dp2:out ds6:in1\nwire dp0:out de7:in0\nwire dp1:out de7:in1\nwire de7:out ds7:in0\nwire dp2:out ds7:in1\nwire ds0:out do0:in0\nwire ds1:out do1:in0\nwire ds2:out do2:in0\nwire ds3:out do3:in0\nwire ds4:out do4:in0\nwire ds5:out do5:in0\nwire ds6:out do6:in0\nwire ds7:out do7:in0\nextin p0 dp0\nextin p1 dp1\nextin p2 dp2\nextout s0 do0\nextout s1 do1\nextout s2 do2\nextout s3 do3\nextout s4 do4\nextout s5 do5\nextout s6 do6\nextout s7 do7\nendchip\n"
}
// 구조적 8x6 ROM: PC[2:0]을 decoder38로 원-핫 푼 뒤, 명령비트 i_k = (그 비트가 1인 주소들의 sel) OR 트리.
// "상수=배선 위상" — INPUT 표·호스트상태·산술 없음. 카운트다운 프로그램을 배선으로 박았다.
// 인코딩(i5 i4 i3 | i2 i1 i0 = op2 op1 op0 | x2 x1 x0): 000 HALT,001 LDI,010 LOAD,011 STORE,100 ADD,101 SUBI,110 JMP,111 JZ; x=하위 3비트.
// 프로그램: 0 LDI3(001 011),1 STORE0(011 000),2 LOAD0(010 000),3 SUBI1(101 001),4 STORE0(011 000),5 JZ7(111 111),6 JMP2(110 010),7 HALT(000 000).
// i0=s0|s3|s5, i1=s0|s5|s6, i2=s5, i3=s0|s1|s3|s4|s5, i4=s1|s2|s4|s5|s6, i5=s3|s5|s6.
// Structural 8x6 ROM (the constant is the wiring): decoder38 one-hots PC, each instr bit is an OR tree of the addresses where it is 1.
function 롬데모() -> string {
"chipdef rom8x6_demo\nnode rp0 INPUT 0 0\nnode rp1 INPUT 0 40\nnode rp2 INPUT 0 80\nchip dec decoder38 120 0\nnode ra0 OR 300 0\nnode ri0 OR 380 0\nnode ra1 OR 300 40\nnode ri1 OR 380 40\nnode ra3 OR 300 120\nnode rb3 OR 380 120\nnode rc3 OR 460 120\nnode ri3 OR 540 120\nnode ra4 OR 300 160\nnode rb4 OR 380 160\nnode rc4 OR 460 160\nnode ri4 OR 540 160\nnode ra5 OR 300 200\nnode ri5 OR 380 200\nnode io0 OUTPUT 620 0\nnode io1 OUTPUT 620 40\nnode io2 OUTPUT 620 80\nnode io3 OUTPUT 620 120\nnode io4 OUTPUT 620 160\nnode io5 OUTPUT 620 200\nwire rp0:out dec:p0\nwire rp1:out dec:p1\nwire rp2:out dec:p2\nwire dec:s0 ra0:in0\nwire dec:s3 ra0:in1\nwire ra0:out ri0:in0\nwire dec:s5 ri0:in1\nwire ri0:out io0:in0\nwire dec:s0 ra1:in0\nwire dec:s5 ra1:in1\nwire ra1:out ri1:in0\nwire dec:s6 ri1:in1\nwire ri1:out io1:in0\nwire dec:s5 io2:in0\nwire dec:s0 ra3:in0\nwire dec:s1 ra3:in1\nwire ra3:out rb3:in0\nwire dec:s3 rb3:in1\nwire rb3:out rc3:in0\nwire dec:s4 rc3:in1\nwire rc3:out ri3:in0\nwire dec:s5 ri3:in1\nwire ri3:out io3:in0\nwire dec:s1 ra4:in0\nwire dec:s2 ra4:in1\nwire ra4:out rb4:in0\nwire dec:s4 rb4:in1\nwire rb4:out rc4:in0\nwire dec:s5 rc4:in1\nwire rc4:out ri4:in0\nwire dec:s6 ri4:in1\nwire ri4:out io4:in0\nwire dec:s3 ra5:in0\nwire dec:s5 ra5:in1\nwire ra5:out ri5:in0\nwire dec:s6 ri5:in1\nwire ri5:out io5:in0\nextin p0 rp0\nextin p1 rp1\nextin p2 rp2\nextout i0 io0\nextout i1 io1\nextout i2 io2\nextout i3 io3\nextout i4 io4\nextout i5 io5\nendchip\n"
}
// 점프 가능 프로그램 카운터: reg4le(PC) + inc4(PC+1) + 비트별 먹스(D=TAKE?TARGET:PC+1; TARGET 3비트→상위비트는 0).
// LD=1이면 에지에 갱신, LD=0이면 유지(HALT). RST→0. 되먹임 Q→inc→먹스→D는 reg4le의 FF에서 끊겨 엔진이 정착시킨다.
// Jumpable PC: reg4le + inc4 + a per-bit mux (D = TAKE ? TARGET : PC+1; TARGET is 3 bits, high bit 0). LD enables update; RST->0.
function 피씨() -> string {
"chipdef pc4le\nnode pt0 INPUT 0 0\nnode pt1 INPUT 0 40\nnode pt2 INPUT 0 80\nnode ptk INPUT 0 120\nnode pld INPUT 0 160\nnode pclk INPUT 0 200\nnode prst INPUT 0 240\nnode pnt NOT 60 120\nnode pa0 AND 200 0\nnode pb0 AND 200 20\nnode pm0 OR 280 0\nnode pa1 AND 200 40\nnode pb1 AND 200 60\nnode pm1 OR 280 40\nnode pa2 AND 200 80\nnode pb2 AND 200 100\nnode pm2 OR 280 80\nnode pa3 AND 200 120\nchip inc inc4 150 320\nchip r reg4le 400 0\nnode pq0 OUTPUT 560 0\nnode pq1 OUTPUT 560 40\nnode pq2 OUTPUT 560 80\nnode pq3 OUTPUT 560 120\nwire ptk:out pnt:in0\nwire pnt:out pa0:in0\nwire inc:s0 pa0:in1\nwire ptk:out pb0:in0\nwire pt0:out pb0:in1\nwire pa0:out pm0:in0\nwire pb0:out pm0:in1\nwire pm0:out r:D0\nwire pnt:out pa1:in0\nwire inc:s1 pa1:in1\nwire ptk:out pb1:in0\nwire pt1:out pb1:in1\nwire pa1:out pm1:in0\nwire pb1:out pm1:in1\nwire pm1:out r:D1\nwire pnt:out pa2:in0\nwire inc:s2 pa2:in1\nwire ptk:out pb2:in0\nwire pt2:out pb2:in1\nwire pa2:out pm2:in0\nwire pb2:out pm2:in1\nwire pm2:out r:D2\nwire pnt:out pa3:in0\nwire inc:s3 pa3:in1\nwire pa3:out r:D3\nwire r:Q0 inc:a0\nwire r:Q1 inc:a1\nwire r:Q2 inc:a2\nwire r:Q3 inc:a3\nwire pld:out r:LD\nwire pclk:out r:CLK\nwire prst:out r:RST\nwire r:Q0 pq0:in0\nwire r:Q1 pq1:in0\nwire r:Q2 pq2:in0\nwire r:Q3 pq3:in0\nextin TARGET0 pt0\nextin TARGET1 pt1\nextin TARGET2 pt2\nextin TAKE ptk\nextin LD pld\nextin CLK pclk\nextin RST prst\nextout Q0 pq0\nextout Q1 pq1\nextout Q2 pq2\nextout Q3 pq3\nendchip\n"
}
// 라이브러리 전체 텍스트(정의 순서: 의존성 먼저 — srlatch→dlatch→dff, 리셋 체인 srlatchr→dlatchr→dffr→reg4,
// inc4→counter4, decoder24→reg4le→ram4, 그다음 CPU 토대 decoder38→rom8x6_demo, pc4le).
// The whole library text (definition order: dependencies first).
// 누산기 데이터패스: ACC(reg4le) + 데이터 RAM(ram4) + ALU(alu4) + B먹스 + ACC.D 3:1 먹스 + acc_zero 플래그.
// 제어선은 전부 extin(내부 상수 없음 — 제어 유닛이 다음 슬라이스에서 구동). 신호는 게이트/칩만(core.gate).
// Accumulator datapath, driven entirely by external control (no internal constants). All from gates/chips.
// 제어 인코딩 / control encoding:
// acc_ld ACC 적재(에지에 ACC.D 캡처) · ram_we RAM 쓰기 · alu_op1 alu_op0 (00 ADD,01 SUB)
// bsrc ALU B 소스: 0=RAM 읽기 q, 1=즉치 imm
// accsrc1 accsrc0 ACC.D 소스: cs1=1→ALU.y; cs1=0&cs0=0→imm(LDI); cs1=0&cs0=1→RAM.q(LOAD)
// imm0..3 즉치 · a0,a1 주소 · acc_zero=NOR(ACC)(JZ용)
// LDI k=cs1=0,cs0=0,acc_ld=1,imm=k · LOAD a=cs1=0,cs0=1,acc_ld=1 · STORE a=ram_we=1
// ADD a=bsrc=0,op=00,cs1=1,acc_ld=1 · SUBI k=bsrc=1,op=01,cs1=1,acc_ld=1,imm=k
function 어큐데이터패스() -> string {
let mut 텍 = "chipdef acc_datapath4\n"
텍 = 텍 + "node cld INPUT 0 0\nnode cwe INPUT 0 30\nnode cop0 INPUT 0 60\nnode cop1 INPUT 0 90\n"
텍 = 텍 + "node cs0 INPUT 0 120\nnode cs1 INPUT 0 150\nnode cbs INPUT 0 180\n"
텍 = 텍 + "node im0 INPUT 0 210\nnode im1 INPUT 0 240\nnode im2 INPUT 0 270\nnode im3 INPUT 0 300\n"
텍 = 텍 + "node ca0 INPUT 0 330\nnode ca1 INPUT 0 360\nnode cclk INPUT 0 390\nnode crst INPUT 0 420\n"
텍 = 텍 + "chip acc reg4le 600 0\nchip ram ram4 600 220\n"
텍 = 텍 + "chip bm0 mux2 300 100\nchip bm1 mux2 300 130\nchip bm2 mux2 300 160\nchip bm3 mux2 300 190\n"
텍 = 텍 + "chip alu alu4 450 100\n"
텍 = 텍 + "chip am0 mux2 760 0\nchip am1 mux2 760 40\nchip am2 mux2 760 80\nchip am3 mux2 760 120\n"
텍 = 텍 + "chip bd0 mux2 860 0\nchip bd1 mux2 860 40\nchip bd2 mux2 860 80\nchip bd3 mux2 860 120\n"
텍 = 텍 + "node z01 OR 700 320\nnode z23 OR 700 360\nnode za OR 760 340\nnode zn NOT 820 340\n"
텍 = 텍 + "node oa0 OUTPUT 980 0\nnode oa1 OUTPUT 980 40\nnode oa2 OUTPUT 980 80\nnode oa3 OUTPUT 980 120\n"
텍 = 텍 + "node oq0 OUTPUT 980 220\nnode oq1 OUTPUT 980 250\nnode oq2 OUTPUT 980 280\nnode oq3 OUTPUT 980 310\nnode oz OUTPUT 980 360\n"
텍 = 텍 + "wire cclk:out acc:CLK\nwire crst:out acc:RST\nwire cld:out acc:LD\n"
텍 = 텍 + "wire bd0:O acc:D0\nwire bd1:O acc:D1\nwire bd2:O acc:D2\nwire bd3:O acc:D3\n"
텍 = 텍 + "wire ca0:out ram:a0\nwire ca1:out ram:a1\nwire cwe:out ram:WE\nwire cclk:out ram:CLK\nwire crst:out ram:RST\n"
텍 = 텍 + "wire acc:Q0 ram:D0\nwire acc:Q1 ram:D1\nwire acc:Q2 ram:D2\nwire acc:Q3 ram:D3\n"
텍 = 텍 + "wire ram:q0 bm0:D0\nwire im0:out bm0:D1\nwire cbs:out bm0:S\n"
텍 = 텍 + "wire ram:q1 bm1:D0\nwire im1:out bm1:D1\nwire cbs:out bm1:S\n"
텍 = 텍 + "wire ram:q2 bm2:D0\nwire im2:out bm2:D1\nwire cbs:out bm2:S\n"
텍 = 텍 + "wire ram:q3 bm3:D0\nwire im3:out bm3:D1\nwire cbs:out bm3:S\n"
텍 = 텍 + "wire acc:Q0 alu:a0\nwire acc:Q1 alu:a1\nwire acc:Q2 alu:a2\nwire acc:Q3 alu:a3\n"
텍 = 텍 + "wire bm0:O alu:b0\nwire bm1:O alu:b1\nwire bm2:O alu:b2\nwire bm3:O alu:b3\n"
텍 = 텍 + "wire cop0:out alu:op0\nwire cop1:out alu:op1\n"
텍 = 텍 + "wire im0:out am0:D0\nwire ram:q0 am0:D1\nwire cs0:out am0:S\n"
텍 = 텍 + "wire im1:out am1:D0\nwire ram:q1 am1:D1\nwire cs0:out am1:S\n"
텍 = 텍 + "wire im2:out am2:D0\nwire ram:q2 am2:D1\nwire cs0:out am2:S\n"
텍 = 텍 + "wire im3:out am3:D0\nwire ram:q3 am3:D1\nwire cs0:out am3:S\n"
텍 = 텍 + "wire am0:O bd0:D0\nwire alu:y0 bd0:D1\nwire cs1:out bd0:S\n"
텍 = 텍 + "wire am1:O bd1:D0\nwire alu:y1 bd1:D1\nwire cs1:out bd1:S\n"
텍 = 텍 + "wire am2:O bd2:D0\nwire alu:y2 bd2:D1\nwire cs1:out bd2:S\n"
텍 = 텍 + "wire am3:O bd3:D0\nwire alu:y3 bd3:D1\nwire cs1:out bd3:S\n"
텍 = 텍 + "wire acc:Q0 z01:in0\nwire acc:Q1 z01:in1\nwire acc:Q2 z23:in0\nwire acc:Q3 z23:in1\n"
텍 = 텍 + "wire z01:out za:in0\nwire z23:out za:in1\nwire za:out zn:in0\n"
텍 = 텍 + "wire acc:Q0 oa0:in0\nwire acc:Q1 oa1:in0\nwire acc:Q2 oa2:in0\nwire acc:Q3 oa3:in0\n"
텍 = 텍 + "wire ram:q0 oq0:in0\nwire ram:q1 oq1:in0\nwire ram:q2 oq2:in0\nwire ram:q3 oq3:in0\nwire zn:out oz:in0\n"
텍 = 텍 + "extin acc_ld cld\nextin ram_we cwe\nextin alu_op0 cop0\nextin alu_op1 cop1\n"
텍 = 텍 + "extin accsrc0 cs0\nextin accsrc1 cs1\nextin bsrc cbs\n"
텍 = 텍 + "extin imm0 im0\nextin imm1 im1\nextin imm2 im2\nextin imm3 im3\nextin a0 ca0\nextin a1 ca1\n"
텍 = 텍 + "extin CLK cclk\nextin RST crst\n"
텍 = 텍 + "extout ACC0 oa0\nextout ACC1 oa1\nextout ACC2 oa2\nextout ACC3 oa3\n"
텍 = 텍 + "extout q0 oq0\nextout q1 oq1\nextout q2 oq2\nextout q3 oq3\nextout acc_zero oz\n"
텍 = 텍 + "endchip\n"
텍
}
// CPU 제어 유닛: opcode 3비트 + acc_zero → 9개 제어선(순수 게이트). decoder38로 opcode를 one-hot으로 풀고
// 제어식을 게이트로 구현. 조합회로(피드백 없음). alu_op1은 항상 0(AND(op0,¬op0)) — CPU는 ADD/SUB만 쓴다.
// 인코딩: opcode=op2 op1 op0 → s0 HALT,s1 LDI,s2 LOAD,s3 STORE,s4 ADD,s5 SUBI,s6 JMP,s7 JZ.
// CPU control unit: opcode + acc_zero -> 9 control lines, pure gates (decoder38 one-hot + control equations).
function 제어유닛() -> string {
"chipdef cpu_control\nnode cop0 INPUT 0 0\nnode cop1 INPUT 0 40\nnode cop2 INPUT 0 80\nnode caz INPUT 0 120\nchip dec decoder38 80 0\nnode cor1 OR 250 0\nnode cor2 OR 250 40\nnode cald OR 330 20\nnode cnop0 NOT 250 100\nnode cz0 AND 330 100\nnode cjz AND 250 160\nnode ctpc OR 330 160\nnode cnh NOT 250 220\nnode oald OUTPUT 430 0\nnode owe OUTPUT 430 40\nnode oa0 OUTPUT 430 80\nnode oa1 OUTPUT 430 120\nnode obs OUTPUT 430 160\nnode oas1 OUTPUT 430 200\nnode oas0 OUTPUT 430 240\nnode otp OUTPUT 430 280\nnode opl OUTPUT 430 320\nwire cop0:out dec:p0\nwire cop1:out dec:p1\nwire cop2:out dec:p2\nwire dec:s1 cor1:in0\nwire dec:s2 cor1:in1\nwire dec:s4 cor2:in0\nwire dec:s5 cor2:in1\nwire cor1:out cald:in0\nwire cor2:out cald:in1\nwire cald:out oald:in0\nwire dec:s3 owe:in0\nwire dec:s5 oa0:in0\nwire dec:s5 obs:in0\nwire cop0:out cnop0:in0\nwire cop0:out cz0:in0\nwire cnop0:out cz0:in1\nwire cz0:out oa1:in0\nwire cor2:out oas1:in0\nwire dec:s2 oas0:in0\nwire dec:s7 cjz:in0\nwire caz:out cjz:in1\nwire dec:s6 ctpc:in0\nwire cjz:out ctpc:in1\nwire ctpc:out otp:in0\nwire dec:s0 cnh:in0\nwire cnh:out opl:in0\nextin op0 cop0\nextin op1 cop1\nextin op2 cop2\nextin acc_zero caz\nextout acc_ld oald\nextout ram_we owe\nextout alu_op0 oa0\nextout alu_op1 oa1\nextout bsrc obs\nextout accsrc0 oas0\nextout accsrc1 oas1\nextout take_pc otp\nextout pc_ld opl\nendchip\n"
}
export function 순차라이브러리() -> string {
에스알래치() + 디래치() + 디플립플롭() + 리셋에스알래치() + 리셋디래치() + 리셋디플립플롭() + 레지스터() + 증분기() + 카운터() + 디코더() + 로드레지스터() + 램() + 디코더38() + 롬데모() + 피씨()
}
// CPU 전용 칩(누산기 데이터패스 등). alu(mux2·alu4)+seq(reg4le·ram4)에 의존하므로 자립이 아니다 —
// main의 `library cpu`가 alu라이브러리+순차라이브러리+이걸 정의 순서대로 앞에 붙인다.
// CPU-only chips (the accumulator datapath, etc.). They depend on the alu (mux2, alu4) and seq (reg4le, ram4)
// libraries, so they are NOT self-contained; main's `library cpu` prepends alu + seq + this, in dependency order.
// 작은 CPU: ROM + PC + 제어 + 데이터패스를 배선한 전체 머신. CLK·RST만 받아 카운트다운 프로그램을 실행한다.
// PC가 ROM을 주소하고, 명령의 opcode는 제어로, operand는 즉치·주소·점프타겟으로 분기한다. imm3는 XOR(clk,clk)=0인
// 구조적 상수(정직한 0, 호스트 상수 없음). 되먹임은 pc/dp의 플립플롭에서 끊기고 엔진이 정착시킨다.
// Tiny CPU: ROM + PC + control + datapath wired into the whole machine; takes only CLK/RST and runs the program.
// PC addresses the ROM; the opcode goes to control, the operand fans out to immediate/address/jump-target.
// imm3 is a structural constant 0 = XOR(clk,clk). Feedback breaks at the pc/dp flip-flops; the engine settles it.
function 틴씨피유() -> string {
"chipdef tinycpu\nnode clk INPUT 20 20\nnode rst INPUT 20 80\nnode zc XOR 20 160\nchip rom rom8x6_demo 140 20\nchip pc pc4le 140 320\nchip cu cpu_control 380 20\nchip dp acc_datapath4 380 320\nnode po0 OUTPUT 680 20\nnode po1 OUTPUT 680 60\nnode po2 OUTPUT 680 100\nnode po3 OUTPUT 680 140\nnode ao0 OUTPUT 680 200\nnode ao1 OUTPUT 680 240\nnode ao2 OUTPUT 680 280\nnode ao3 OUTPUT 680 320\nnode mo0 OUTPUT 680 380\nnode mo1 OUTPUT 680 420\nnode mo2 OUTPUT 680 460\nnode mo3 OUTPUT 680 500\nwire pc:Q0 rom:p0\nwire pc:Q1 rom:p1\nwire pc:Q2 rom:p2\nwire rom:i3 cu:op0\nwire rom:i4 cu:op1\nwire rom:i5 cu:op2\nwire rom:i0 dp:imm0\nwire rom:i0 dp:a0\nwire rom:i0 pc:TARGET0\nwire rom:i1 dp:imm1\nwire rom:i1 dp:a1\nwire rom:i1 pc:TARGET1\nwire rom:i2 dp:imm2\nwire rom:i2 pc:TARGET2\nwire clk:out zc:in0\nwire clk:out zc:in1\nwire zc:out dp:imm3\nwire dp:acc_zero cu:acc_zero\nwire cu:acc_ld dp:acc_ld\nwire cu:ram_we dp:ram_we\nwire cu:alu_op0 dp:alu_op0\nwire cu:alu_op1 dp:alu_op1\nwire cu:bsrc dp:bsrc\nwire cu:accsrc0 dp:accsrc0\nwire cu:accsrc1 dp:accsrc1\nwire cu:take_pc pc:TAKE\nwire cu:pc_ld pc:LD\nwire clk:out pc:CLK\nwire clk:out dp:CLK\nwire rst:out pc:RST\nwire rst:out dp:RST\nwire pc:Q0 po0:in0\nwire pc:Q1 po1:in0\nwire pc:Q2 po2:in0\nwire pc:Q3 po3:in0\nwire dp:ACC0 ao0:in0\nwire dp:ACC1 ao1:in0\nwire dp:ACC2 ao2:in0\nwire dp:ACC3 ao3:in0\nwire dp:q0 mo0:in0\nwire dp:q1 mo1:in0\nwire dp:q2 mo2:in0\nwire dp:q3 mo3:in0\nextin CLK clk\nextin RST rst\nextout PC0 po0\nextout PC1 po1\nextout PC2 po2\nextout PC3 po3\nextout ACC0 ao0\nextout ACC1 ao1\nextout ACC2 ao2\nextout ACC3 ao3\nextout M0 mo0\nextout M1 mo1\nextout M2 mo2\nextout M3 mo3\nendchip\n"
}
export function 씨피유라이브러리() -> string {
어큐데이터패스() + 제어유닛() + 틴씨피유()
}