slow.monster

STUDIO HAZE · jeniapp

Topaz source

Open by house policy. Topaz is our own language.

Topaz 5.20.2 · 63 files

apps/circuit/main.tpz

Topaz · 15124 B · Original file

1// 회로 시뮬레이터. 회로 줄과 지시 줄을 나눠 읽고, 칩을 원시 게이트로 펼쳐 평가한 뒤 지금 보는 단계를 그립니다.
2// 입력은 node, wire, chip, chipdef 줄과 지시 줄 tick 번호 상태, now 틱, probe 노드, fault 노드 고장, truthtable, export, library 이름, view 인스턴스..., prev 번호 상태 이고, 비면 반가산기 예시를 씁니다.
3// 출력은 빵부스러기, 요약, 회로 SVG 와 주석 구분자 뒤에 붙는 파형, 진리표, 정착 상태, 내보내기 조각입니다. 상태는 아이디=0|1 을 쉼표로 이은 글입니다.
4// 같은 입력이면 늘 같은 출력이 나옵니다. 되먹임 회로는 prev 로 받은 이전 정착 상태에서만 이어 갑니다.
5// 상태를 들지 않습니다. 페이지가 정착 상태를 다음 입력의 prev 로 되돌려 줍니다.
6// Circuit simulator entry: split netlist and directive lines, flatten and evaluate, then render the current view.
8import core.hierarchy
9import core.sim
10import core.history
11import core.netlist
12import core.util
13import core.fault
14import core.truthtable
15import ui.render
16import ui.chrome
17import ui.waveform
18import ui.inspector
19import app.serialize
20import chips.arith
21import chips.alu
22import chips.seq
24type 라이브러리 = "arith" | "alu" | "seq" | "cpu" | "unknown"
25type 요청 = {
26 회로글: string,
27 이력: Array<Array<netlist.입력값>>,
28 프로브들: Array<string>,
29 현재틱: int,
30 고장줄들: Array<Array<string>>,
31 진리표요청: bool,
32 내보내기요청: bool,
33 라이브러리들: Array<라이브러리>,
34 뷰경로: Array<string>,
35 이전상태: Array<sim.노드신호>,
36 이전있음: bool,
39// 틱 수와 정착 스텝 수를 막아 되먹임 회로가 정착하지 않아도 끝나게 합니다. 페이지는 틱을 더 짧게 유지합니다.
40let 틱상한 = 400
41let 정착상한 = 256
43let 기본회로 = "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"
45// 모르는 라이브러리 이름은 무시합니다.
46function 라이브러리로(이름: string) -> 라이브러리 {
47 match 이름 {
48 case "arith" => "arith"
49 case "alu" => "alu"
50 case "seq" => "seq"
51 case "cpu" => "cpu"
52 case _ => "unknown"
53 }
56// 쌍마다 = 로 나뉜 두 부분이 아니면 버립니다. 1 이 아닌 값은 모두 0 입니다.
57function 입력상태읽기(글: string) -> Array<netlist.입력값> {
58 [for 부분 in 글.split(",").map(쌍 => 쌍.split("=")) if 부분.length == 2 => ({ 아이디: 부분[0], 값: if 부분[1] == "1" { 1 } else { 0 } })]
61function 정착상태읽기(글: string) -> Array<sim.노드신호> {
62 입력상태읽기(글).map(입력 => ({ 아이디: 입력.아이디, 신호: 입력.값 == 1, 프레임: 0 }))
65// 페이지가 <!--state--> 뒤에서 읽어 다음 틱의 prev 로 되돌려 줍니다.
66function 정착상태글(신호들: Array<sim.노드신호>) -> string {
67 신호들.map(신호값 => 신호값.아이디 + "=" + (if 신호값.신호 { "1" } else { "0" })).join(",")
70// 줄 하나의 지시입니다. 모양이 모자란 tick, now, probe, prev 줄과 version 줄은 없음입니다.
71enum 지시줄 {
72 틱(Array<netlist.입력값>),
73 현재(int),
74 프로브(string),
75 고장(Array<string>),
76 진리표,
77 내보내기,
78 라이브러리줄(라이브러리),
79 보기(Array<string>),
80 이전(Array<sim.노드신호>),
81 회로(string),
82 없음,
85// 모르는 머리는 회로 줄로 넘겨 계층 파서가 판단합니다.
86function 지시읽기(줄: string) -> 지시줄 {
87 let 토큰들 = util.토큰나누기(줄)
88 let 머리 = if 토큰들.length > 0 { 토큰들[0] } else { "" }
89 match 머리 {
90 case "tick" => if 토큰들.length >= 3 { 지시줄.틱(입력상태읽기(토큰들[2])) } else { 지시줄.없음 }
91 case "now" => if 토큰들.length >= 2 { 지시줄.현재(toInt(토큰들[1]) ?? -1) } else { 지시줄.없음 }
92 case "probe" => if 토큰들.length >= 2 { 지시줄.프로브(토큰들[1]) } else { 지시줄.없음 }
93 case "fault" => 지시줄.고장(토큰들)
94 case "truthtable" => 지시줄.진리표
95 case "export" => 지시줄.내보내기
96 case "library" => 지시줄.라이브러리줄(if 토큰들.length >= 2 { 라이브러리로(토큰들[1]) } else { "unknown" })
97 case "view" => 지시줄.보기(토큰들.slice(1, 토큰들.length))
98 case "version" => 지시줄.없음
99 case "prev" => if 토큰들.length >= 3 { 지시줄.이전(정착상태읽기(토큰들[2])) } else { 지시줄.없음 }
100 case _ => 지시줄.회로(줄)
101 }
104function 틱값(지시: 지시줄) -> Array<Array<netlist.입력값>> {
105 match 지시 {
106 case 틱(값) => [값]
107 case 현재(_) | 프로브(_) | 고장(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 보기(_) | 이전(_) | 회로(_) | 없음 => []
108 }
111function 현재값(지시: 지시줄) -> Array<int> {
112 match 지시 {
113 case 현재(값) => [값]
114 case 틱(_) | 프로브(_) | 고장(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 보기(_) | 이전(_) | 회로(_) | 없음 => []
115 }
118function 프로브값(지시: 지시줄) -> Array<string> {
119 match 지시 {
120 case 프로브(값) => [값]
121 case 틱(_) | 현재(_) | 고장(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 보기(_) | 이전(_) | 회로(_) | 없음 => []
122 }
125function 고장값(지시: 지시줄) -> Array<Array<string>> {
126 match 지시 {
127 case 고장(값) => [값]
128 case 틱(_) | 현재(_) | 프로브(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 보기(_) | 이전(_) | 회로(_) | 없음 => []
129 }
132function 진리표값(지시: 지시줄) -> Array<bool> {
133 match 지시 {
134 case 진리표 => [true]
135 case 틱(_) | 현재(_) | 프로브(_) | 고장(_) | 내보내기 | 라이브러리줄(_) | 보기(_) | 이전(_) | 회로(_) | 없음 => []
136 }
139function 내보내기값(지시: 지시줄) -> Array<bool> {
140 match 지시 {
141 case 내보내기 => [true]
142 case 틱(_) | 현재(_) | 프로브(_) | 고장(_) | 진리표 | 라이브러리줄(_) | 보기(_) | 이전(_) | 회로(_) | 없음 => []
143 }
146function 라이브러리값(지시: 지시줄) -> Array<라이브러리> {
147 match 지시 {
148 case 라이브러리줄(값) => [값]
149 case 틱(_) | 현재(_) | 프로브(_) | 고장(_) | 진리표 | 내보내기 | 보기(_) | 이전(_) | 회로(_) | 없음 => []
150 }
153function 보기값(지시: 지시줄) -> Array<Array<string>> {
154 match 지시 {
155 case 보기(값) => [값]
156 case 틱(_) | 현재(_) | 프로브(_) | 고장(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 이전(_) | 회로(_) | 없음 => []
157 }
160function 이전값(지시: 지시줄) -> Array<Array<sim.노드신호>> {
161 match 지시 {
162 case 이전(값) => [값]
163 case 틱(_) | 현재(_) | 프로브(_) | 고장(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 보기(_) | 회로(_) | 없음 => []
164 }
167function 회로값(지시: 지시줄) -> Array<string> {
168 match 지시 {
169 case 회로(값) => [값]
170 case 틱(_) | 현재(_) | 프로브(_) | 고장(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 보기(_) | 이전(_) | 없음 => []
171 }
174// view 줄이 여럿이면 경로를 이어 붙입니다. 같은 지시가 다시 나오면 now 와 prev 는 마지막 줄을 쓰고, tick 은 앞에서부터 틱상한 줄까지만 씁니다.
175function 요청읽기(원본: string) -> 요청 {
176 let 지시들 = 원본.split("\n").map(지시읽기)
177 let 현재틱들 = [for 지시 in 지시들 for 틱 in 현재값(지시) => 틱]
178 let 이전상태들 = [for 지시 in 지시들 for 상태 in 이전값(지시) => 상태]
179 {
180 회로글: [for 지시 in 지시들 for 줄 in 회로값(지시) => 줄 + "\n"].join(""),
181 이력: [for 지시 in 지시들 for 입력 in 틱값(지시) => 입력].slice(0, 틱상한),
182 프로브들: [for 지시 in 지시들 for 프로브 in 프로브값(지시) => 프로브],
183 현재틱: if 현재틱들.length > 0 { 현재틱들[현재틱들.length - 1] } else { -1 },
184 고장줄들: [for 지시 in 지시들 for 토큰들 in 고장값(지시) => 토큰들],
185 진리표요청: [for 지시 in 지시들 for 요청함 in 진리표값(지시) => 요청함].length > 0,
186 내보내기요청: [for 지시 in 지시들 for 요청함 in 내보내기값(지시) => 요청함].length > 0,
187 라이브러리들: [for 지시 in 지시들 for 이름 in 라이브러리값(지시) => 이름],
188 뷰경로: [for 지시 in 지시들 for 경로 in 보기값(지시) for 조각 in 경로 => 조각],
189 이전상태: if 이전상태들.length > 0 { 이전상태들[이전상태들.length - 1] } else { [] },
190 이전있음: 이전상태들.length > 0,
191 }
194function 요청했는가(라이브러리들: Array<라이브러리>, 이름: 라이브러리) -> bool {
195 라이브러리들.filter(요청 => 요청 == 이름).length > 0
198// alu 는 arith 를 품으므로 둘 중 하나만 붙이고 seq 는 더합니다. cpu 는 alu 와 seq 에 CPU 칩까지 정의 순서대로 붙입니다.
199function 라이브러리글(라이브러리들: Array<라이브러리>) -> string {
200 if 요청했는가(라이브러리들, "cpu") { return alu.산술논리라이브러리() + seq.순차라이브러리() + seq.처리장치라이브러리() }
201 let 산술글 = if 요청했는가(라이브러리들, "alu") { alu.산술논리라이브러리() } else if 요청했는가(라이브러리들, "arith") { arith.산술라이브러리() } else { "" }
202 산술글 + (if 요청했는가(라이브러리들, "seq") { seq.순차라이브러리() } else { "" })
205// 되먹임 회로에서는 조합 전용 패널이 틀린 값을 보이므로 안내로 바꿉니다.
206function 파형패널(요청값: 요청, 되먹임: bool, 고장평탄: netlist.회로) -> string {
207 if 요청값.프로브들.length == 0 || 요청값.이력.length == 0 { return "" }
208 if 되먹임 { return "<div class=\"warn\">파형은 조합 회로 전용입니다 — 순차 회로의 신호는 시간축을 스크럽해 보세요.</div>" }
209 let 틱 = if 요청값.현재틱 >= 0 && 요청값.현재틱 < 요청값.이력.length { 요청값.현재틱 } else { 요청값.이력.length - 1 }
210 waveform.그리기파형(history.파형계산(고장평탄, 요청값.이력, 요청값.프로브들), 틱)
213// 진리표는 고장 없는 회로로 계산한 정답이라 고장 난 회로와 견줄 수 있습니다.
214function 검사패널(요청값: 요청, 되먹임: bool, 평탄: netlist.회로, 고장있음: bool) -> string {
215 if !요청값.진리표요청 { return "" }
216 if 되먹임 { return "<div class=\"warn\">진리표는 조합 회로 전용입니다 — 순차 회로의 출력은 현재 상태에 따라 달라집니다.</div>" }
217 inspector.그리기검사(truthtable.진리표만들기(평탄), 고장있음)
220function 정착배너(상태: sim.정착상태) -> string {
221 match 상태 {
222 case "oscillating" => "<div class=\"warn\">회로가 발진합니다 — 정착하지 않는 되먹임입니다.</div>"
223 case "unsettled" => "<div class=\"warn\">회로가 정착하지 않았습니다 — 스텝 한도를 넘었습니다.</div>"
224 case "settled" => ""
225 }
228// 페이지가 가를 수 있게 패널마다 주석 구분자를 앞에 둡니다. 비어 있는 패널은 구분자도 붙이지 않습니다.
229function 구분해붙이기(구분자: string, 패널: string) -> string {
230 if 패널 == "" { "" } else { 구분자 + 패널 }
233// Err 는 결과 대신 보여 줄 오류 조각입니다.
234function 회로보기(원본: string) -> Result<string, string> {
235 let 요청값 = 요청읽기(원본)
236 let 원래분해 = match hierarchy.분해(라이브러리글(요청값.라이브러리들) + 요청값.회로글) {
237 case Ok(값) => 값
238 case Err(오류) => return Err(util.오류표시("넷리스트 오류: " + 오류))
239 }
240 // now 가 이력 안을 가리키면 그 틱의 입력 상태로 회로를 그리고 평가합니다.
241 let 분해값 = if 요청값.현재틱 >= 0 && 요청값.현재틱 < 요청값.이력.length {
242 let 원래본체 = 원래분해.본체
243 원래분해{ 본체: 원래본체{ 노드들: netlist.입력적용(원래본체.노드들, 요청값.이력[요청값.현재틱]) } }
244 } else {
245 원래분해
246 }
247 let 평탄 = hierarchy.평탄화(분해값)
248 let 고장들 = match fault.고장파싱(요청값.고장줄들) {
249 case Ok(값) => 값
250 case Err(오류) => return Err(util.오류표시(오류))
251 }
252 match fault.고장검증(고장들, 평탄.노드들) {
253 case Ok(_) => {}
254 case Err(오류) => return Err(util.오류표시(오류))
255 }
256 let 고장평탄 = fault.고장회로(평탄, 고장들)
257 // prev 가 있거나 회로에 되먹임이 있으면 단위 지연으로 정착시키고, 아니면 조합 평가를 그대로 씁니다.
258 let 조합 = sim.평가회로(고장평탄)
259 let 순차 = 요청값.이전있음 || sim.되먹임인가(조합)
260 let 정착 = if 순차 { sim.정착평가회로(고장평탄, 요청값.이전상태, 정착상한) } else { { 시뮬: 조합, 상태: "settled", 스텝: 조합.프레임수 } }
261 let 보기 = match hierarchy.경로뷰(분해값, 정착.시뮬, 요청값.뷰경로, 고장들) {
262 case Ok(값) => 값
263 case Err(오류) => return Err(util.오류표시(오류))
264 }
265 let 단계 = 보기.단계
266 let 요약 = "<div class=\"meta\">노드 {단계.분해.본체.노드들.length}개, 칩 {단계.분해.본체.인스턴스들.length}개. 입력을 토글하거나 칩을 더블클릭하고, 전선을 눌러 파형을 봅니다.</div>"
267 // 되먹임 판정은 고장 없는 회로로 합니다.
268 let 되먹임 = sim.되먹임인가(sim.평가회로(평탄))
269 let 상태출력 = if 순차 { "<!--state-->" + 정착상태글(정착.시뮬.신호들) + "<!--simmeta-->status={정착.상태},steps={정착.스텝}" } else { "" }
270 let 내보내기 = if 요청값.내보내기요청 { "<!--내보내기-->" + serialize.직렬화(원래분해) } else { "" }
271 Ok([
272 chrome.빵부스러기(보기.이름들),
273 요약,
274 정착배너(정착.상태),
275 render.그리기뷰(단계.분해, 단계.시뮬, fault.고장노드들(단계.고장들)),
276 구분해붙이기("<!--파형-->", 파형패널(요청값, 되먹임, 고장평탄)),
277 구분해붙이기("<!--검사-->", 검사패널(요청값, 되먹임, 평탄, 고장들.length > 0)),
278 상태출력,
279 내보내기,
280 ].join(""))
283function 처리(입력: string) -> string {
284 let 원본 = if 입력.trim() == "" { 기본회로 } else { 입력 }
285 util.결과풀기(회로보기(원본))
288input() |> 처리 |> print

SHA256 12c000e21630c3708d20c1f25cb8737672e76861086ec3b797c918edea5b417c