slow.monster

STUDIO HAZE · jeniapp

Topaz source

Open by house policy. Topaz is our own language.

Topaz 5.20.2 · 63 files

apps/circuit/ui/render.tpz

Topaz · 13727 B · Original file

1// 회로도 그리기. 지금 보는 단계의 원시 노드, 칩 상자, 전선을 SVG 로 그리고 신호가 흐르는 곳은 계산한 신호 색으로 칠합니다.
2// 입력은 그 단계의 분해 결과, 평탄 시뮬 결과, 고장 노드 아이디 목록이고 출력은 svg 조각 하나입니다.
3// 같은 입력이면 늘 같은 조각이 나옵니다. 좌표 산술은 표시용이고 신호는 core.sim 이 계산한 값만 읽습니다.
4// 노드, 핀, 전선, 칩마다 data-* 아이디를 달 뿐 히트 판정과 일시 상태는 다루지 않습니다. 페이지가 합니다.
5// Schematic renderer: primitive nodes, chip boxes and wires as SVG colored by the computed signals.
7import core.netlist
8import core.sim
9import core.hierarchy
10import ui.routing
11import ui.signalviz
12import ui.components
14type 상자 = { 왼: int, 위: int, 오른: int, 아래: int }
16let 칩폭 = 96
17let 빈노드: netlist.노드 = { 아이디: "", 종류: "INPUT", x: 0, y: 0, 값: 0 }
18// 노드도 칩도 없으면 이 크기의 빈 판을 그립니다.
19let 기본판: 상자 = { 왼: 0, 위: 0, 오른: 320, 아래: 200 }
21// SEG7 선분 a 부터 g 까지의 양 끝 상대 좌표입니다.
22let 선분들 = [
23 { x1: 10, y1: 6, x2: 30, y2: 6 },
24 { x1: 32, y1: 8, x2: 32, y2: 26 },
25 { x1: 32, y1: 32, x2: 32, y2: 50 },
26 { x1: 10, y1: 52, x2: 30, y2: 52 },
27 { x1: 8, y1: 32, x2: 8, y2: 50 },
28 { x1: 8, y1: 8, x2: 8, y2: 26 },
29 { x1: 10, y1: 29, x2: 30, y2: 29 },
32// 핀 이름이 여러 번 나오면 마지막 자리를 씁니다.
33function 마지막자리(이름들: Array<string>, 이름: string) -> int {
34 let 자리들 = [for i in 0..<이름들.length if 이름들[i] == 이름 => i]
35 if 자리들.length == 0 { -1 } else { 자리들[자리들.length - 1] }
38// 핀의 연결점은 몸체 바로 바깥이고 전선과 페이지의 히트 영역이 여기에 붙습니다. 모르는 입력 핀은 in0 자리에 둡니다.
39function 핀위치(노드값: netlist.노드, 핀: string) -> routing.점 {
40 let x = 노드값.x
41 let y = 노드값.y
42 match 노드값.종류 {
43 case "INPUT" => ({ x: x + 46, y: y + 15 })
44 case "OUTPUT" => ({ x: x - 6, y: y + 15 })
45 case "SEG7" => ({ x: x - 6, y: y + 6 + 마지막자리(netlist.세그핀들, 핀).atLeast(0) * 8 })
46 case "NAND" | "AND" | "OR" | "NOT" | "XOR" | "NOR" | "XNOR" => {
47 if 핀 == "out" {
48 { x: x + 76, y: y + 25 }
49 } else if 핀 == "in1" {
50 { x: x - 6, y: y + 35 }
51 } else if netlist.입력핀들(노드값.종류).length == 2 {
52 { x: x - 6, y: y + 15 }
53 } else {
54 { x: x - 6, y: y + 25 }
55 }
56 }
57 }
60// 아이디, 핀, 종류는 파서가 검증한 안전한 토큰이라 이스케이프하지 않습니다.
61function 핀점(아이디: string, 핀: string, 위치: routing.점, 색: string) -> string {
62 "<circle class=\"pin\" data-node-id=\"{아이디}\" data-pin=\"{핀}\" cx=\"{위치.x}\" cy=\"{위치.y}\" r=\"4\" fill=\"{색}\" />"
65function 핀선(시작: routing.점, 끝: routing.점, 색: string) -> string {
66 "<line x1=\"{시작.x}\" y1=\"{시작.y}\" x2=\"{끝.x}\" y2=\"{끝.y}\" stroke=\"{색}\" stroke-width=\"2\" />"
69function 게이트그리기(노드값: netlist.노드, 시뮬: sim.시뮬결과, 고장들: Array<string>) -> string {
70 let x = 노드값.x
71 let y = 노드값.y
72 let 출력색 = signalviz.원천색(시뮬, 노드값.아이디)
73 let 외곽 = if 노드값.아이디 in 고장들 { signalviz.고장색 } else { signalviz.먹 }
74 let 출력 = 핀위치(노드값, "out")
75 let 입력핀들 = netlist.입력핀들(노드값.종류)
76 let 입력조각들 = 입력핀들.map(핀 => {
77 let 위치 = 핀위치(노드값, 핀)
78 핀선(위치, { x: x, y: 위치.y }, signalviz.흑연) + 핀점(노드값.아이디, 핀, 위치, signalviz.흑연)
79 })
80 [
81 "<g class=\"node gate\" data-node-id=\"{노드값.아이디}\">",
82 "<rect x=\"{x}\" y=\"{y}\" width=\"70\" height=\"50\" rx=\"4\" fill=\"{signalviz.벨럼}\" stroke=\"{외곽}\" stroke-width=\"2\" />",
83 "<text x=\"{x + 35}\" y=\"{y + 29}\" text-anchor=\"middle\" font-size=\"13\" font-family=\"monospace\" fill=\"{외곽}\">{노드값.종류}</text>",
84 핀선({ x: x + 70, y: 출력.y }, 출력, 출력색),
85 핀점(노드값.아이디, "out", 출력, 출력색),
86 입력조각들.join(""),
87 "</g>",
88 ].join("")
91function 세그그리기(노드값: netlist.노드, 값들: Array<bool>) -> string {
92 let x = 노드값.x
93 let y = 노드값.y
94 let 선분조각들 = map(0..<선분들.length, i => "<line x1=\"{x + 선분들[i].x1}\" y1=\"{y + 선분들[i].y1}\" x2=\"{x + 선분들[i].x2}\" y2=\"{y + 선분들[i].y2}\" stroke=\"{signalviz.신호색(값들[i])}\" stroke-width=\"4\" stroke-linecap=\"round\" />")
95 let 핀조각들 = map(0..<netlist.세그핀들.length, i => 핀점(노드값.아이디, netlist.세그핀들[i], { x: x - 6, y: y + 6 + i * 8 }, signalviz.신호색(값들[i])))
96 [
97 "<g class=\"node seg7\" data-node-id=\"{노드값.아이디}\">",
98 "<rect x=\"{x - 4}\" y=\"{y - 2}\" width=\"44\" height=\"62\" rx=\"4\" fill=\"{signalviz.벨럼}\" stroke=\"{signalviz.먹}\" stroke-width=\"2\" />",
99 선분조각들.join(""),
100 핀조각들.join(""),
101 "</g>",
102 ].join("")
105// 칩 출력 핀이 구동하는 선분은 칩 안의 원시 노드를 평탄 시뮬에서 읽습니다. 핀 하나에 전선이 여럿이면 마지막 전선을 씁니다.
106function 세그값들(분해값: hierarchy.분해결과, 시뮬: sim.시뮬결과, 아이디: string) -> Array<bool> {
107 netlist.세그핀들.map(핀 => 분해값.본체.전선들.reduce(false, (값, 선) => if 선.끝노드 == 아이디 && 선.끝핀 == 핀 {
108 sim.신호로(시뮬, hierarchy.평탄원천(분해값, 선.시작노드, 선.시작핀).노드).신호
109 } else {
110 값
111 }))
114// 입력과 출력의 몸체는 ui.components 가 그리고 여기서 핀을 더합니다. 입력 핀은 자기 신호 색, 출력 핀은 흑연색입니다.
115function 노드그리기(분해값: hierarchy.분해결과, 시뮬: sim.시뮬결과, 고장들: Array<string>, 노드값: netlist.노드) -> string {
116 match 노드값.종류 {
117 case "INPUT" => {
118 let 켜짐 = sim.신호로(시뮬, 노드값.아이디).신호
119 let 색 = signalviz.신호색(켜짐)
120 let 출력 = 핀위치(노드값, "out")
121 components.입력몸체(노드값, 켜짐) + 핀선({ x: 노드값.x + 40, y: 노드값.y + 15 }, 출력, 색) + 핀점(노드값.아이디, "out", 출력, 색)
122 }
123 case "OUTPUT" => {
124 let 입력 = 핀위치(노드값, "in0")
125 components.출력몸체(노드값, sim.신호로(시뮬, 노드값.아이디).신호) + 핀선(입력, { x: 노드값.x, y: 노드값.y + 15 }, signalviz.흑연) + 핀점(노드값.아이디, "in0", 입력, signalviz.흑연)
126 }
127 case "SEG7" => 세그그리기(노드값, 세그값들(분해값, 시뮬, 노드값.아이디))
128 case "NAND" | "AND" | "OR" | "NOT" | "XOR" | "NOR" | "XNOR" => 게이트그리기(노드값, 시뮬, 고장들)
129 }
132function 칩높이(정의: hierarchy.칩정의) -> int {
133 정의.외부입력.length.atLeast(정의.외부출력.length) * 30 + 16
136// extin 은 왼쪽, extout 은 오른쪽에 30 간격으로 놓습니다.
137function 칩핀위치(칩: hierarchy.놓인칩, 핀: string) -> routing.점 {
138 let 배치 = 칩.배치
139 let 입력자리 = 마지막자리(칩.정의.외부입력.map(핀값 => 핀값.핀), 핀)
140 if 입력자리 >= 0 {
141 { x: 배치.x - 6, y: 배치.y + 30 + 입력자리 * 30 }
142 } else {
143 { x: 배치.x + 칩폭 + 6, y: 배치.y + 30 + 마지막자리(칩.정의.외부출력.map(핀값 => 핀값.핀), 핀) * 30 }
144 }
147// 전선마다 노드를 훑지 않도록 그리기 전에 한 번 만듭니다. 앞에서부터 넣으므로 같은 아이디가 여럿이면 마지막 노드가 남습니다.
148function 노드표만들기(노드들: Array<netlist.노드>) -> Map<string, netlist.노드> {
149 let mut 표: Map<string, netlist.노드> = Map.new()
150 for 노드값 in 노드들 { 표.insert(노드값.아이디, 노드값) }
151 표
154// 전선 양 끝은 파서가 있음을 보장합니다.
155function 끝점위치(분해값: hierarchy.분해결과, 노드표: Map<string, netlist.노드>, 노드: string, 핀: string) -> routing.점 {
156 match hierarchy.놓인칩찾기(분해값, 노드) {
157 case Some(칩) => 칩핀위치(칩, 핀)
158 case None => 핀위치(노드표.getOr(노드, 빈노드), 핀)
159 }
162// 칩 안 게이트 하나라도 고장이면 상자를 고장색으로 칠합니다. 평탄 아이디가 인스턴스 접두로 시작합니다.
163function 칩고장인가(고장들: Array<string>, 칩아이디: string) -> bool {
164 고장들.filter(고장노드 => 고장노드.startsWith(칩아이디 + "__")).length > 0
167function 칩상자그리기(분해값: hierarchy.분해결과, 시뮬: sim.시뮬결과, 고장들: Array<string>, 칩: hierarchy.놓인칩) -> string {
168 let x = 칩.배치.x
169 let y = 칩.배치.y
170 let 아이디 = 칩.배치.아이디
171 let 높이 = 칩높이(칩.정의)
172 let 외곽 = if 칩고장인가(고장들, 아이디) { signalviz.고장색 } else { signalviz.먹 }
173 let 입력조각들 = 칩.정의.외부입력.map(핀값 => {
174 let 위치 = 칩핀위치(칩, 핀값.핀)
175 let 색 = signalviz.원천색(시뮬, hierarchy.칩입력원천(분해값, 아이디, 핀값.핀).노드)
176 핀선(위치, { x: x, y: 위치.y }, 색) + "<text x=\"{x + 6}\" y=\"{위치.y + 4}\" font-size=\"9\" font-family=\"monospace\" fill=\"{signalviz.흑연}\">{핀값.핀}</text>" + 핀점(아이디, 핀값.핀, 위치, 색)
177 })
178 let 출력조각들 = 칩.정의.외부출력.map(핀값 => {
179 let 위치 = 칩핀위치(칩, 핀값.핀)
180 let 색 = signalviz.원천색(시뮬, hierarchy.평탄원천(분해값, 아이디, 핀값.핀).노드)
181 핀선({ x: x + 칩폭, y: 위치.y }, 위치, 색) + "<text x=\"{x + 칩폭 - 6}\" y=\"{위치.y + 4}\" text-anchor=\"end\" font-size=\"9\" font-family=\"monospace\" fill=\"{signalviz.흑연}\">{핀값.핀}</text>" + 핀점(아이디, 핀값.핀, 위치, 색)
182 })
183 [
184 "<g class=\"node chip\" data-chip-id=\"{아이디}\">",
185 "<rect x=\"{x}\" y=\"{y}\" width=\"{칩폭}\" height=\"{높이}\" rx=\"5\" fill=\"{signalviz.벨럼}\" stroke=\"{외곽}\" stroke-width=\"3\" />",
186 "<rect x=\"{x + 4}\" y=\"{y + 4}\" width=\"{칩폭 - 8}\" height=\"{높이 - 8}\" rx=\"3\" fill=\"none\" stroke=\"{signalviz.흑연}\" stroke-width=\"1\" />",
187 "<text x=\"{x + 칩폭 / 2}\" y=\"{y + 높이 / 2 + 4}\" text-anchor=\"middle\" font-size=\"12\" font-family=\"sans-serif\" fill=\"{외곽}\">{칩.배치.칩이름}</text>",
188 입력조각들.join(""),
189 출력조각들.join(""),
190 "</g>",
191 ].join("")
194// 전선의 아이디는 도착 끝점입니다. 입력 핀 하나는 전선 하나만 받으므로 그리는 순서와 상관없이 유일합니다.
195function 전선그리기(분해값: hierarchy.분해결과, 노드표: Map<string, netlist.노드>, 시뮬: sim.시뮬결과, 선: netlist.전선) -> string {
196 let 경로 = routing.직각경로(끝점위치(분해값, 노드표, 선.시작노드, 선.시작핀), 끝점위치(분해값, 노드표, 선.끝노드, 선.끝핀))
197 let 원천 = hierarchy.평탄원천(분해값, 선.시작노드, 선.시작핀).노드
198 "<polyline class=\"wire\" data-wire-id=\"{선.끝노드}:{선.끝핀}\"{signalviz.원천프레임(시뮬, 원천)} points=\"{경로}\" fill=\"none\" stroke=\"{signalviz.원천색(시뮬, 원천)}\" stroke-width=\"2\" />"
201function 격자(판: 상자) -> string {
202 "<defs><pattern id=\"grid\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\"><path d=\"M20 0H0V20\" fill=\"none\" stroke=\"{signalviz.먹}\" stroke-opacity=\"0.05\" stroke-width=\"1\" /></pattern></defs><rect x=\"{판.왼}\" y=\"{판.위}\" width=\"{판.오른 - 판.왼}\" height=\"{판.아래 - 판.위}\" fill=\"url(#grid)\" />"
205function 상자합치기(가: 상자, 나: 상자) -> 상자 {
206 { 왼: 가.왼.atMost(나.왼), 위: 가.위.atMost(나.위), 오른: 가.오른.atLeast(나.오른), 아래: 가.아래.atLeast(나.아래) }
209// 판 크기는 노드와 칩 상자를 모두 덮는 경계 상자라 음수 좌표와 팬도 덮습니다. 전선을 먼저 깔고 노드와 칩을 위에 그립니다.
210export function 그리기뷰(분해값: hierarchy.분해결과, 시뮬: sim.시뮬결과, 고장들: Array<string>) -> string {
211 let 칩들 = hierarchy.놓인칩들(분해값)
212 let 노드표 = 노드표만들기(분해값.본체.노드들)
213 let 노드상자들 = 분해값.본체.노드들.map(노드값 => ({ 왼: 노드값.x - 12, 위: 노드값.y - 22, 오른: 노드값.x + 86, 아래: 노드값.y + 62 }))
214 let 칩상자들 = 칩들.map(칩 => ({ 왼: 칩.배치.x - 16, 위: 칩.배치.y - 12, 오른: 칩.배치.x + 칩폭 + 16, 아래: 칩.배치.y + 칩높이(칩.정의) + 12 }))
215 let 상자들 = [for 묶음 in [노드상자들, 칩상자들] for 상자값 in 묶음 => 상자값]
216 let 판 = if 상자들.length == 0 { 기본판 } else { 상자들.reduce(상자들[0], 상자합치기) }
217 let 폭 = 판.오른 - 판.왼
218 let 높이 = 판.아래 - 판.위
219 [
220 "<svg class=\"schematic\" viewBox=\"{판.왼} {판.위} {폭} {높이}\" width=\"{폭}\" height=\"{높이}\" xmlns=\"http://www.w3.org/2000/svg\">",
221 격자(판),
222 분해값.본체.전선들.map(선 => 전선그리기(분해값, 노드표, 시뮬, 선)).join(""),
223 분해값.본체.노드들.map(노드값 => 노드그리기(분해값, 시뮬, 고장들, 노드값)).join(""),
224 칩들.map(칩 => 칩상자그리기(분해값, 시뮬, 고장들, 칩)).join(""),
225 "</svg>",
226 ].join("")

SHA256 72e7cb2b0cd58cf24c8408bcb620c0b4661bf35573990d64a086e88f3cfdb21a