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.7
import core.netlist8
import core.sim9
import core.hierarchy10
import ui.routing11
import ui.signalviz12
import ui.components14
type 상자 = { 왼: int, 위: int, 오른: int, 아래: int }16
let 칩폭 = 9617
let 빈노드: netlist.노드 = { 아이디: "", 종류: "INPUT", x: 0, y: 0, 값: 0 }18
// 노드도 칩도 없으면 이 크기의 빈 판을 그립니다.19
let 기본판: 상자 = { 왼: 0, 위: 0, 오른: 320, 아래: 200 }21
// SEG7 선분 a 부터 g 까지의 양 끝 상대 좌표입니다.22
let 선분들 = [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 },30
]32
// 핀 이름이 여러 번 나오면 마지막 자리를 씁니다.33
function 마지막자리(이름들: Array<string>, 이름: string) -> int {34
let 자리들 = [for i in 0..<이름들.length if 이름들[i] == 이름 => i]35
if 자리들.length == 0 { -1 } else { 자리들[자리들.length - 1] }36
}38
// 핀의 연결점은 몸체 바로 바깥이고 전선과 페이지의 히트 영역이 여기에 붙습니다. 모르는 입력 핀은 in0 자리에 둡니다.39
function 핀위치(노드값: netlist.노드, 핀: string) -> routing.점 {40
let x = 노드값.x41
let y = 노드값.y42
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
}58
}60
// 아이디, 핀, 종류는 파서가 검증한 안전한 토큰이라 이스케이프하지 않습니다.61
function 핀점(아이디: string, 핀: string, 위치: routing.점, 색: string) -> string {62
"<circle class=\"pin\" data-node-id=\"{아이디}\" data-pin=\"{핀}\" cx=\"{위치.x}\" cy=\"{위치.y}\" r=\"4\" fill=\"{색}\" />"63
}65
function 핀선(시작: routing.점, 끝: routing.점, 색: string) -> string {66
"<line x1=\"{시작.x}\" y1=\"{시작.y}\" x2=\"{끝.x}\" y2=\"{끝.y}\" stroke=\"{색}\" stroke-width=\"2\" />"67
}69
function 게이트그리기(노드값: netlist.노드, 시뮬: sim.시뮬결과, 고장들: Array<string>) -> string {70
let x = 노드값.x71
let y = 노드값.y72
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("")89
}91
function 세그그리기(노드값: netlist.노드, 값들: Array<bool>) -> string {92
let x = 노드값.x93
let y = 노드값.y94
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("")103
}105
// 칩 출력 핀이 구동하는 선분은 칩 안의 원시 노드를 평탄 시뮬에서 읽습니다. 핀 하나에 전선이 여럿이면 마지막 전선을 씁니다.106
function 세그값들(분해값: hierarchy.분해결과, 시뮬: sim.시뮬결과, 아이디: string) -> Array<bool> {107
netlist.세그핀들.map(핀 => 분해값.본체.전선들.reduce(false, (값, 선) => if 선.끝노드 == 아이디 && 선.끝핀 == 핀 {108
sim.신호로(시뮬, hierarchy.평탄원천(분해값, 선.시작노드, 선.시작핀).노드).신호109
} else {110
값111
}))112
}114
// 입력과 출력의 몸체는 ui.components 가 그리고 여기서 핀을 더합니다. 입력 핀은 자기 신호 색, 출력 핀은 흑연색입니다.115
function 노드그리기(분해값: 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
}130
}132
function 칩높이(정의: hierarchy.칩정의) -> int {133
정의.외부입력.length.atLeast(정의.외부출력.length) * 30 + 16134
}136
// extin 은 왼쪽, extout 은 오른쪽에 30 간격으로 놓습니다.137
function 칩핀위치(칩: 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
}145
}147
// 전선마다 노드를 훑지 않도록 그리기 전에 한 번 만듭니다. 앞에서부터 넣으므로 같은 아이디가 여럿이면 마지막 노드가 남습니다.148
function 노드표만들기(노드들: Array<netlist.노드>) -> Map<string, netlist.노드> {149
let mut 표: Map<string, netlist.노드> = Map.new()150
for 노드값 in 노드들 { 표.insert(노드값.아이디, 노드값) }151
표152
}154
// 전선 양 끝은 파서가 있음을 보장합니다.155
function 끝점위치(분해값: hierarchy.분해결과, 노드표: Map<string, netlist.노드>, 노드: string, 핀: string) -> routing.점 {156
match hierarchy.놓인칩찾기(분해값, 노드) {157
case Some(칩) => 칩핀위치(칩, 핀)158
case None => 핀위치(노드표.getOr(노드, 빈노드), 핀)159
}160
}162
// 칩 안 게이트 하나라도 고장이면 상자를 고장색으로 칠합니다. 평탄 아이디가 인스턴스 접두로 시작합니다.163
function 칩고장인가(고장들: Array<string>, 칩아이디: string) -> bool {164
고장들.filter(고장노드 => 고장노드.startsWith(칩아이디 + "__")).length > 0165
}167
function 칩상자그리기(분해값: hierarchy.분해결과, 시뮬: sim.시뮬결과, 고장들: Array<string>, 칩: hierarchy.놓인칩) -> string {168
let x = 칩.배치.x169
let y = 칩.배치.y170
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("")192
}194
// 전선의 아이디는 도착 끝점입니다. 입력 핀 하나는 전선 하나만 받으므로 그리는 순서와 상관없이 유일합니다.195
function 전선그리기(분해값: 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\" />"199
}201
function 격자(판: 상자) -> 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)\" />"203
}205
function 상자합치기(가: 상자, 나: 상자) -> 상자 {206
{ 왼: 가.왼.atMost(나.왼), 위: 가.위.atMost(나.위), 오른: 가.오른.atLeast(나.오른), 아래: 가.아래.atLeast(나.아래) }207
}209
// 판 크기는 노드와 칩 상자를 모두 덮는 경계 상자라 음수 좌표와 팬도 덮습니다. 전선을 먼저 깔고 노드와 칩을 위에 그립니다.210
export 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("")227
}SHA256 72e7cb2b0cd58cf24c8408bcb620c0b4661bf35573990d64a086e88f3cfdb21a