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.8
import core.hierarchy9
import core.sim10
import core.history11
import core.netlist12
import core.util13
import core.fault14
import core.truthtable15
import ui.render16
import ui.chrome17
import ui.waveform18
import ui.inspector19
import app.serialize20
import chips.arith21
import chips.alu22
import chips.seq24
type 라이브러리 = "arith" | "alu" | "seq" | "cpu" | "unknown"25
type 요청 = {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,37
}39
// 틱 수와 정착 스텝 수를 막아 되먹임 회로가 정착하지 않아도 끝나게 합니다. 페이지는 틱을 더 짧게 유지합니다.40
let 틱상한 = 40041
let 정착상한 = 25643
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"45
// 모르는 라이브러리 이름은 무시합니다.46
function 라이브러리로(이름: string) -> 라이브러리 {47
match 이름 {48
case "arith" => "arith"49
case "alu" => "alu"50
case "seq" => "seq"51
case "cpu" => "cpu"52
case _ => "unknown"53
}54
}56
// 쌍마다 = 로 나뉜 두 부분이 아니면 버립니다. 1 이 아닌 값은 모두 0 입니다.57
function 입력상태읽기(글: string) -> Array<netlist.입력값> {58
[for 부분 in 글.split(",").map(쌍 => 쌍.split("=")) if 부분.length == 2 => ({ 아이디: 부분[0], 값: if 부분[1] == "1" { 1 } else { 0 } })]59
}61
function 정착상태읽기(글: string) -> Array<sim.노드신호> {62
입력상태읽기(글).map(입력 => ({ 아이디: 입력.아이디, 신호: 입력.값 == 1, 프레임: 0 }))63
}65
// 페이지가 <!--state--> 뒤에서 읽어 다음 틱의 prev 로 되돌려 줍니다.66
function 정착상태글(신호들: Array<sim.노드신호>) -> string {67
신호들.map(신호값 => 신호값.아이디 + "=" + (if 신호값.신호 { "1" } else { "0" })).join(",")68
}70
// 줄 하나의 지시입니다. 모양이 모자란 tick, now, probe, prev 줄과 version 줄은 없음입니다.71
enum 지시줄 {72
틱(Array<netlist.입력값>),73
현재(int),74
프로브(string),75
고장(Array<string>),76
진리표,77
내보내기,78
라이브러리줄(라이브러리),79
보기(Array<string>),80
이전(Array<sim.노드신호>),81
회로(string),82
없음,83
}85
// 모르는 머리는 회로 줄로 넘겨 계층 파서가 판단합니다.86
function 지시읽기(줄: 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
}102
}104
function 틱값(지시: 지시줄) -> Array<Array<netlist.입력값>> {105
match 지시 {106
case 틱(값) => [값]107
case 현재(_) | 프로브(_) | 고장(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 보기(_) | 이전(_) | 회로(_) | 없음 => []108
}109
}111
function 현재값(지시: 지시줄) -> Array<int> {112
match 지시 {113
case 현재(값) => [값]114
case 틱(_) | 프로브(_) | 고장(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 보기(_) | 이전(_) | 회로(_) | 없음 => []115
}116
}118
function 프로브값(지시: 지시줄) -> Array<string> {119
match 지시 {120
case 프로브(값) => [값]121
case 틱(_) | 현재(_) | 고장(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 보기(_) | 이전(_) | 회로(_) | 없음 => []122
}123
}125
function 고장값(지시: 지시줄) -> Array<Array<string>> {126
match 지시 {127
case 고장(값) => [값]128
case 틱(_) | 현재(_) | 프로브(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 보기(_) | 이전(_) | 회로(_) | 없음 => []129
}130
}132
function 진리표값(지시: 지시줄) -> Array<bool> {133
match 지시 {134
case 진리표 => [true]135
case 틱(_) | 현재(_) | 프로브(_) | 고장(_) | 내보내기 | 라이브러리줄(_) | 보기(_) | 이전(_) | 회로(_) | 없음 => []136
}137
}139
function 내보내기값(지시: 지시줄) -> Array<bool> {140
match 지시 {141
case 내보내기 => [true]142
case 틱(_) | 현재(_) | 프로브(_) | 고장(_) | 진리표 | 라이브러리줄(_) | 보기(_) | 이전(_) | 회로(_) | 없음 => []143
}144
}146
function 라이브러리값(지시: 지시줄) -> Array<라이브러리> {147
match 지시 {148
case 라이브러리줄(값) => [값]149
case 틱(_) | 현재(_) | 프로브(_) | 고장(_) | 진리표 | 내보내기 | 보기(_) | 이전(_) | 회로(_) | 없음 => []150
}151
}153
function 보기값(지시: 지시줄) -> Array<Array<string>> {154
match 지시 {155
case 보기(값) => [값]156
case 틱(_) | 현재(_) | 프로브(_) | 고장(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 이전(_) | 회로(_) | 없음 => []157
}158
}160
function 이전값(지시: 지시줄) -> Array<Array<sim.노드신호>> {161
match 지시 {162
case 이전(값) => [값]163
case 틱(_) | 현재(_) | 프로브(_) | 고장(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 보기(_) | 회로(_) | 없음 => []164
}165
}167
function 회로값(지시: 지시줄) -> Array<string> {168
match 지시 {169
case 회로(값) => [값]170
case 틱(_) | 현재(_) | 프로브(_) | 고장(_) | 진리표 | 내보내기 | 라이브러리줄(_) | 보기(_) | 이전(_) | 없음 => []171
}172
}174
// view 줄이 여럿이면 경로를 이어 붙입니다. 같은 지시가 다시 나오면 now 와 prev 는 마지막 줄을 쓰고, tick 은 앞에서부터 틱상한 줄까지만 씁니다.175
function 요청읽기(원본: 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
}192
}194
function 요청했는가(라이브러리들: Array<라이브러리>, 이름: 라이브러리) -> bool {195
라이브러리들.filter(요청 => 요청 == 이름).length > 0196
}198
// alu 는 arith 를 품으므로 둘 중 하나만 붙이고 seq 는 더합니다. cpu 는 alu 와 seq 에 CPU 칩까지 정의 순서대로 붙입니다.199
function 라이브러리글(라이브러리들: Array<라이브러리>) -> string {200
if 요청했는가(라이브러리들, "cpu") { return alu.산술논리라이브러리() + seq.순차라이브러리() + seq.처리장치라이브러리() }201
let 산술글 = if 요청했는가(라이브러리들, "alu") { alu.산술논리라이브러리() } else if 요청했는가(라이브러리들, "arith") { arith.산술라이브러리() } else { "" }202
산술글 + (if 요청했는가(라이브러리들, "seq") { seq.순차라이브러리() } else { "" })203
}205
// 되먹임 회로에서는 조합 전용 패널이 틀린 값을 보이므로 안내로 바꿉니다.206
function 파형패널(요청값: 요청, 되먹임: 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.파형계산(고장평탄, 요청값.이력, 요청값.프로브들), 틱)211
}213
// 진리표는 고장 없는 회로로 계산한 정답이라 고장 난 회로와 견줄 수 있습니다.214
function 검사패널(요청값: 요청, 되먹임: bool, 평탄: netlist.회로, 고장있음: bool) -> string {215
if !요청값.진리표요청 { return "" }216
if 되먹임 { return "<div class=\"warn\">진리표는 조합 회로 전용입니다 — 순차 회로의 출력은 현재 상태에 따라 달라집니다.</div>" }217
inspector.그리기검사(truthtable.진리표만들기(평탄), 고장있음)218
}220
function 정착배너(상태: sim.정착상태) -> string {221
match 상태 {222
case "oscillating" => "<div class=\"warn\">회로가 발진합니다 — 정착하지 않는 되먹임입니다.</div>"223
case "unsettled" => "<div class=\"warn\">회로가 정착하지 않았습니다 — 스텝 한도를 넘었습니다.</div>"224
case "settled" => ""225
}226
}228
// 페이지가 가를 수 있게 패널마다 주석 구분자를 앞에 둡니다. 비어 있는 패널은 구분자도 붙이지 않습니다.229
function 구분해붙이기(구분자: string, 패널: string) -> string {230
if 패널 == "" { "" } else { 구분자 + 패널 }231
}233
// Err 는 결과 대신 보여 줄 오류 조각입니다.234
function 회로보기(원본: 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(""))281
}283
function 처리(입력: string) -> string {284
let 원본 = if 입력.trim() == "" { 기본회로 } else { 입력 }285
util.결과풀기(회로보기(원본))286
}288
input() |> 처리 |> printSHA256 12c000e21630c3708d20c1f25cb8737672e76861086ec3b797c918edea5b417c