From 89a5298f47730fd5d972037296580c485a2e3f20 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Fri, 5 Jun 2026 10:07:26 -0300 Subject: [PATCH] test(chipbus): live proof - a real Z80 boots from a ROM over the bus End-to-end validation of Phases 0-2 on an actual CPU. The real Z80 (examples/intel/z80.c) and a 32K EPROM (z80-boot-rom.c, a rom-32k variant holding JP 0x0006 / HALT) are wired chip-to-chip over a shared address + data bus with no board. RD drives the ROM's OE; CE is left enabled. Booting exercises all three phases at once: the Z80 drives the address -> the ROM reacts on the shared net key (Phase 0); asserts RD -> the ROM tri-state-drives the data bus while the Z80 released it (Phase 1); and reads the data bus in the SAME tickTimers step, getting the settled byte (Phase 2 settle-before-read). The Z80 fetches C3,06,00, jumps to 0x0006, fetches 76, and HALTs -> drives HALT low, which the test observes. z80.wasm is compiled from the committed examples/intel/z80.c; the boot ROM source + chip.json live in test_custom_chips/sdk/examples. All 36 chipbus tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../__tests__/chipbus-z80-rom-boot.test.ts | 127 ++++++++++++++++++ .../fixtures/chipbus/z80-boot-rom.wasm | Bin 0 -> 54384 bytes .../src/__tests__/fixtures/chipbus/z80.wasm | Bin 0 -> 37284 bytes .../sdk/examples/z80-boot-rom.c | 104 ++++++++++++++ .../sdk/examples/z80-boot-rom.chip.json | 9 ++ 5 files changed, 240 insertions(+) create mode 100644 frontend/src/__tests__/chipbus-z80-rom-boot.test.ts create mode 100644 frontend/src/__tests__/fixtures/chipbus/z80-boot-rom.wasm create mode 100644 frontend/src/__tests__/fixtures/chipbus/z80.wasm create mode 100644 test/test_custom_chips/sdk/examples/z80-boot-rom.c create mode 100644 test/test_custom_chips/sdk/examples/z80-boot-rom.chip.json diff --git a/frontend/src/__tests__/chipbus-z80-rom-boot.test.ts b/frontend/src/__tests__/chipbus-z80-rom-boot.test.ts new file mode 100644 index 00000000..70257acc --- /dev/null +++ b/frontend/src/__tests__/chipbus-z80-rom-boot.test.ts @@ -0,0 +1,127 @@ +/** + * Phase 0-2 LIVE end-to-end proof on a REAL CPU (project/multichip-bus/). + * + * A real Z80 (examples/intel/z80.c) and a 32K EPROM (z80-boot-rom.c, a rom-32k + * variant) are wired chip-to-chip over a shared address + data bus, NO board. + * The ROM holds `JP 0x0006 / HALT`. Booting it requires the Z80 to: + * - drive the address bus -> ROM reacts (shared net key, Phase 0); + * - assert RD -> ROM tri-state-drives the data bus while the Z80 released it + * (Phase 1 drive resolution); + * - read the data bus IN THE SAME tickTimers step and get the settled byte + * (Phase 2 settle-before-read); + * fetch C3,06,00 (the JP + target), jump, fetch 76 at 0x0006, and HALT — which + * drives HALT low. Observing HALT go low is the proof the whole bus works. + * + * Fixtures compiled with wasi-sdk (see test_intel/scripts/compile-chip.sh flags); + * skips cleanly if absent. z80.wasm from examples/intel/z80.c; z80-boot-rom.wasm + * from project/multichip-bus research (rom-32k with a boot image). + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { readFileSync, existsSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { PinManager } from '../simulation/PinManager'; +import { ChipInstance } from '../simulation/customChips/ChipRuntime'; +import { + resolveChipNetKey, + setChipBusEnabledForTest, + resetChipNetIndexForTest, + type ChipNetState, +} from '../simulation/customChips/chipNets'; +import { syntheticChipPin } from '../simulation/customChips/syntheticPins'; +import { resetBusNets } from '../simulation/customChips/busNets'; + +const z80Path = fileURLToPath(new URL('./fixtures/chipbus/z80.wasm', import.meta.url)); +const romPath = fileURLToPath(new URL('./fixtures/chipbus/z80-boot-rom.wasm', import.meta.url)); +const haveFixtures = existsSync(z80Path) && existsSync(romPath); + +const range = (n: number, from = 0) => Array.from({ length: n }, (_, i) => i + from); + +const Z80_PINS = [ + ...range(16).map((i) => `A${i}`), + ...range(8).map((i) => `D${i}`), + 'M1', 'MREQ', 'IORQ', 'RD', 'WR', 'RFSH', 'HALT', 'WAIT', + 'INT', 'NMI', 'RESET', 'BUSREQ', 'BUSACK', 'CLK', 'VCC', 'GND', +]; +const ROM_PINS = [ + ...range(15).map((i) => `A${i}`), + ...range(8).map((i) => `D${i}`), + 'CE', 'OE', 'VCC', 'GND', +]; + +// The schematic: address A0..A14 and data D0..D7 straight across, plus the Z80's +// RD strobe into the ROM's OE so the ROM only drives during reads. CE is left +// unwired (reads 0 = always enabled). +const STATE: ChipNetState = { + wires: [ + ...range(15).map((i) => ({ + start: { componentId: 'z80', pinName: `A${i}` }, + end: { componentId: 'rom', pinName: `A${i}` }, + })), + ...range(8).map((i) => ({ + start: { componentId: 'z80', pinName: `D${i}` }, + end: { componentId: 'rom', pinName: `D${i}` }, + })), + { start: { componentId: 'z80', pinName: 'RD' }, end: { componentId: 'rom', pinName: 'OE' } }, + ], + components: [ + { id: 'z80', metadataId: 'custom-chip' }, + { id: 'rom', metadataId: 'custom-chip' }, + ], + boards: [], +}; + +function pinKey(chipId: string, pin: string): number { + return resolveChipNetKey(STATE, chipId, pin) ?? syntheticChipPin(chipId, pin); +} +function wiresFor(chipId: string, pins: string[]): Map { + return new Map(pins.map((p) => [p, pinKey(chipId, p)] as [string, number])); +} + +describe.skipIf(!haveFixtures)('chipbus Phase 0-2 — a real Z80 boots from a ROM over the bus', () => { + beforeEach(() => { + setChipBusEnabledForTest(true); + resetChipNetIndexForTest(); + resetBusNets(); + }); + afterEach(() => { + setChipBusEnabledForTest(null); + resetChipNetIndexForTest(); + resetBusNets(); + }); + + it('fetches JP+HALT from the ROM across the bus and drives HALT low', async () => { + const z80Wasm = new Uint8Array(readFileSync(z80Path)); + const romWasm = new Uint8Array(readFileSync(romPath)); + const pm = new PinManager(); + + const z80 = await ChipInstance.create({ + wasm: z80Wasm, componentId: 'z80', pinManager: pm, wires: wiresFor('z80', Z80_PINS), + }); + z80.start(); + const rom = await ChipInstance.create({ + wasm: romWasm, componentId: 'rom', pinManager: pm, wires: wiresFor('rom', ROM_PINS), + }); + rom.start(); + + const halt = pinKey('z80', 'HALT'); + // At power-on the Z80 holds HALT high (running, registered OUTPUT_HIGH). + expect(pm.getPinState(halt)).toBe(true); + + // on_clock bails while BUSREQ/WAIT read low (unwired inputs default 0), so + // deassert them, then release RESET (rising edge) to start the CPU. + pm.triggerPinChange(pinKey('z80', 'BUSREQ'), true); + pm.triggerPinChange(pinKey('z80', 'WAIT'), true); + pm.triggerPinChange(pinKey('z80', 'RESET'), true); + + // Run the 4 MHz pseudo-clock (250 ns period) for ~200 ticks — far more than + // the 2 instructions to reach HALT. + z80.tickTimers(50_000n); + + // HALT went low: the Z80 executed JP 0x0006 then HALT, having correctly read + // every byte from the ROM over the shared chip-to-chip bus. + expect(pm.getPinState(halt)).toBe(false); + + z80.dispose(); + rom.dispose(); + }); +}); diff --git a/frontend/src/__tests__/fixtures/chipbus/z80-boot-rom.wasm b/frontend/src/__tests__/fixtures/chipbus/z80-boot-rom.wasm new file mode 100644 index 0000000000000000000000000000000000000000..3e2ef522058ea1994b5d972bc07d15160c9aad5e GIT binary patch literal 54384 zcmeHP378bswZ3($s%Pn;XL?!@Tn9x!aRvr(1Vmv#T;N3$7bFRJO;2~tOiNF9+sgoM zGa_nSVlZ(*lh`I=TtW;a@(kjFVxkf>#ubV0Neod;l;j!o5k-CHoLgJhbPsJzzI^X{ zcl@U6-gD1A=Rg0sTU8CRNVZ2|j43003wd9k(zj6SgCDj~!7@nLLdF)VDB@Y4Y9xDo zJw{I|vy?NAwff#A;dDG1&KO46jm9)aSuNsiG_xEf5s-yYva5$cf#WO}U7w*g_qq%r08O}vI5{ANR zquudz7>wuB%wMhOHB}00SNuvttr z)HLcmQ1Uk^AgepCoi&j0HqM%qcI~_o3UpS^JDj)IpE87*-N9u06WiJGtgaoMxO;6u zOdu1E+n;!&q}FpDtsNz`Ui4_aSyF4OM{8$Et=BwSe=MoB-J|tRNv$0otz9LxcA^%f z@Us5pc(-VM#}xM$i~nMZuPzqvHpN#Ii}#pfV0o`7j^Q*K+ST?;X_&AE^FD^(KpG_) zR+ek{Zn=ir%QdVj*Kl{ahSlX7)|6|wzg)x5%QZYyu3=-jh9}B3JXNki30QFiKe zr!}*gt-#4PLh-jNBPft9lzXM&GAkJ*faOs+zhSv0d(Nvw0#xcP(tFXXM*`5}#d=%4 zdL#fnwOH>puO10NPb=2j?$sj!=;_6JJG^=%06kx^-cGL`2|%x^SC9gmO*?Bg*C*wa zA-GSu=wuF^f;yK#aO4sQGF$=?wM!tva|uKoE`b1S3IJM@wRrmdocRN+;VMj}xcYsp z!Pijh_utyj6-L|nihh2^Y&L5)JLU9~*t}V@7-O4!aACc8kw20F$$(@)G9Vd{3`hnf z1CjyBfMh^2AQ_MhNCqSWk^#wpWI!??8ITM}1|$QL0m*=5Kr$d1kPJu$Bm06n;K|Uq93u;1{p~8qvns zzq)k@0BJ0XsvcVQE89AF-x}x_DBE}N*3ZM$;HW;2<-yqqLaal}#yhy*!7&eR`Jabs zaLb=3gM(W}Hq2Sf`GA>%EyK$4jFt~DTh#%5vE|@F`yN!bFSaxd+IPgDeXUmC094pA zYEa9Em9>m5Bg^(hCWo7?1DV8@LkI2q_hte8T9`}wwOaK8sM3$M5)K{_&Z9%%7gteb zKwr$S)v66>#g@a(z5`d_&pYbMuai~av#zY)&$BWNJ{JaGHwO0$aIJX+J1N%a=X?4P zbGMCjbYzTPC8!)32P3VNB@9m=ex(AggErYZ?BgMT=Jx|XPzenIs!j`GWiqh8-vO>RFJ+WAuM_`h z2#%bnHhkh;b8_l6MBKM9Pi|otxMZ!sfYxH>5q&fPJiWYkU-5{jI)oC@=$r5Vvj1G@ zTJ3_v9-Zzt$7ej*nffUVHI2rtraT6xoI_YGo^{|yhm#mCIKPH6Z-vY6fimR_>K!mo zt27NyhdRkqFrt-HeU@(3yDBuX!f$G!UjRGeB?5S$86AE1pxZ71Yzpr`eaBR>*`8*gjbr!aDTi7oJD!%pP~azj*=CPC zpK=_sR|lqvy;I54eBey5cNN-bFXaiQq&*_0z>MK%c|4uvc)AkVJbRkynHR|OIpq0h z%kw<){Jib?47B+L$MYQU=StrGF84WjX5b4Q_g7+|3%_9px&(aih0ARBN5B%mBUW84 zJcJW0(u+i#YQgCsI1vb65OF#B_^Jnv2a}tUJ#aSQJswOw9!xzJ23{acNv!)iEet8b z@CO@%A2D>5z#zsmo@1>!It5jic-${R_dXo3lZmd5y>DH4afUR<5}Pk#Dq|TuV+qv7O}6#uc^` z*D!H=idBQwO&(7-Ii6m`_M3O3r~k4%tt3y~u+j^T`cUWkQl8w&e-_qHzRKfimE&nG zGQEl&C{|EjSc|L6omltnwLJfjJilRk{v+D_p5vLu9$rMA+<_i|bgc2XU*otZO4rt* zy8}U$2E$Lu-Ish;pe<O{r-vL*No;?ZM)hRN(<9_*gY9V=>OA0h za$<7N@u|37ZuWTE?09O$IsGeVi5ZRcEmmDEd<7Ht%+E09c@~mq3CV0ARwVaa^s%WJ zNsVWY&GtM7ZSE@eET(uZ zLUU&%i(^Sq(7&I~CbShO3IafZQlOkDN*~0E0$!k`a0Ro10y8UI!L*>jOpB^uUh1B; zKxzu1Tn0l%@Ut9Ue^dgcYXlKq_o)5_X0zUlx894lUco`+!rS1&OUY{x@ZPcD1qHkj zKrg`?@*;YwB`I;|xv&9L-iedpU@x?TUC@4uxjb0GWC~~>VKNU^0U4c&rQXM8qcqbz>~=#|ZCp zVZ9Jc{SVq15#o6MnMtYY^0(Yyp}g z0KH}dIvqW=x&YZExR?Ob_PR73XyG%x&}I^}x|ro>mhf{DSQmkxj@?dl{D_#{6u04` zI@fD>t}A95jB`aq%Pdd-COq9|dzy`QPH{ZB;&4;^Fixp?UU&0c?r1j8^UmgTy|d$7 zG2(8w0L>SGrh|PE`ZMV1OCCVZ6cJ3ou(l~a2g*F^g%))|qm?eIybYG?9s$R+(!~f_ z-D?&QLjd`W38d;n3sE&bEvkVxpk9L}nE99)*aemPzrw|Q2pp0@Bws59U7*G{Sk{KM z8Q1WX7j?>on$AFJQW)n`7-CLShBFGD9_l`{;>wD+hQW!9rgY8|*FA9H5lsIc zA)HqVu9xl+F5thz-5V^MD+?>oy58pn-tPr|sS7xr5SA%;B)~|t0v!=9vjw_1BP>_& zkWkkKp-{>$7onYPg?5D}wB<1KL}&~bj-};8qiJi;U6Gy9t{^$i^KZL?ybRauZ@Yqg z4r#c-737uJunWc&zgfhz0_wlR`bts1&x-LDPmK2X zJRi)9U_)s4+Va5`Z-gS)BVEDXfPU|C1xp#d%SNsgW@4~+xq_vkc(*Iqxu|is^07Hs zwE~^1?@{m|jVac}!TMfX;47SoU4EqCAs;oW#1gz(*@H{)7Kop6{$s(%A1yxa^YF0* z7DYkBoft-8A2iH?xSdmgYq7)k*4Es77}v!0UP9Kp2%-4b+wt=PZNLxO8?aiS4fsKO z12*^D4Py1HhDQmC?jaG~D5%q9eb^J-Jb0@jqI(`O-M^3MFw+AUNEGs?z2Q9V4d-bw zGEcIwZ5G%%ENs8>V0+KThU*&NR0`X`!L|jTw_fssdC3KaCelkTZfPRDY-^}uBE2l& z_5(bcNUsREw?UmI(pFJ_$b$c>2fjU#9)v@);P`dK_FSnroCVP}WR8Hats!#(re}vc zES!`(>?jnmOzyCw;3A4Yh$yyMQM@To@3EqIOVo$@%mK2qTonEA0YgM_9YT41@F)fj zl7Gho(QbDDxP0xl@hSxx^ScG!Nq~n2@cRPanHIheJopZPLxh0Uiw<^`f+fJXPaCfz zc?x&-nCo$FD||J4{lm2aJ{{rS#d(b>;Dw2}nyB14@Y3d}ae7opTkv!~3X}F@8`XR0 zX>Tc1BGC;99tgD)9YMzdjwo0mg;WRl7MuWdYRBV&H6%Kbz@ZdG-E|g#0|9~)9Sq0^4~1{e{B07g@%vx_;<}2_rdL9AdADhh~ksPWEY5g zV2J?@=ZWheBzy{Yo)DkJv`^u0Lk2J<<}y2#(vAYychJW1d@E?w?X+-D6S&vG1FPWe zS;R7-6mEw`R~YUG-KTJ1pXv?cR96_^M+m2K=Rvm~!#K?q#v>TUY5e`-Fy@HS>w`BA zv^&fds29QabAkF*bUkx$)C0%bRGduk=B1eTc`m?fkd1jm{`@>B4F_1XMP zD6YY7_o^#kl2%po7)VY8iS`Aag5r8?yi+|JMqHH^L{}}?*r}40d1&Q?U@L$*1I4R& z;5JYR-V2vnSY5Rz)keGz)7Gk?jj-_Vga&P$%0lZP7dY&-2UQ-zV+@{Qv`5wI5WX9v z$}iLgMjoF+FF`ekF+Y!1SExaZ;Xo|^O$h{{Yw!@b?16cyovAhAeTFth3$2G9-$cX5 zXe`tQynF{Mty* z@J75>YpZz(mncl72Cs`kc;3O3YU{Yh0yS`k3+@k>x6s2~dL!PewN-lPm(U4wrQNBs z(1$?66wuOssB0`Z8%npL{vG;$bl1MGhql8>egUfd0PqF7q4X{Sy;*OhyLP=EQXyV) zeIv94mV-+DRd5O32$!!S=x2B%-mA681?ctAp#9Pf`d0}05$uWBh_h|T)8BXi38!2K z(A0JwoCBaQ!17ie#36Ym;`vw&;Mz${E~u) zLxZ*sl3#!baV}~bAo)*1@?+StQL7EDfqbX2@=FcI8;+}Qpx?L{?duTW`C9NI(CEeT z9aLXd4V-8Y;j{|8)xkHcjoTA8jl;(fd} zQFm}oHk0)xS~)=nEAK(+#~9aGy%FzYv@x#a9%bqeg#}GJ8ubr{Qa|dC*7w7ElXiq2 zS^|UNEL0f@`X9i4c@I{Ogvy^GbgJOdI2Z=;R0OwIYs7n#_NErXZ)o^kOM_>F&|ct> zCcvM8MEu4@mG@x=VesEWvma{1MDSn7;9t>#>p=P~v^rF8q`OwHhrS8fz)WiUp<~SN zUR0T@2mTY(DZgFVzeA_;8Z4hB$^|U<=rmX!!*X6{X96ybJpgNz^)+}sZ2P|fC)PJ? z7`%+*a*hS-?2v&DO07zVj3*)IO6}>;$~5t*nB9nF=;m0($Dnf{Z0j3y8u#Jzp(p6r zR_}$fqN_O&_lIspFQ-MqZjazpKL0+2*5{CurI)l zZl2}hJw_^@WBFt@-jy_BO%PiqS7bF3jT&hbRb{TJb-HHb7Z{((fV%^kF_OLUOez_6H|&d%Qhmr+6#kqV$8DvMy!u6mDjPBu z7NJcBGLd8~)l(s@Mar%qa3)n~LPX)}KxVS}j%+j&Pv_#mZXW)#r-d~qTq2LMt(u4@ z7e!O~WX|br^cwD}Wz6nkk5xw^hMir{#Jjq4S&zlc;?6__2BWRjnaz15iBxJ)KJ5`^ zQXYY~4xtf29<^-TBgl9K*_cP8(-!1m8A`)SZ-cd1fQ8->Vn$$s_M{z=k;x=ePPK@3 z+gOp*Os>78;W7l1&Sj&hCb9#I4D1oH#hEzl6Q%8zR-x{>rD<0$%xAF#f{a2c;x46l zG8J=GaaE2MNfvh-$*4;M_L{i69*Gs#DeQR60S|cXK%#NjRHIJaQ$@rvBaw?Z8u_e| ziA6|)xtk0vcyDbp3vH9k!o&$s0DD=6OKU!B^A5|l7{;*2gvAK1U=PF9zC<^epgrMe zB#{Wm;8c)hNq5S#3<##72U{A>31$vF22&y|B+drsGH*5QYxyh&WxB|kmf_?gBpIua zN3(P`95cjPn94Aif^c;R#EAlg zR{$IAJIWT)jFHUubQl%)H?{2pO|(0bVYw888DxQ?168&=pNpjyCo8~JtZ!~e43?y5 zJPV6In6@kv@u{>?iB(4z&a?^Wg0muwg`}}qoKmrZqk~kj(vj~ZjdUiQjKKbdG8j-a z^RNtK$}ac7Xkk&jMDXGyBxfWN6+{i$Ak9o~B*BunQCT(!PIhUJDcjd{@TgN_NruV( zEH+&Fj*-t|z#RmHmo*HGXf-vNiS%(}fn~6%NVD(BR4LEgIfU#KHnma|a9Cx(}EJ3uTx>+XEwC7c3$J7Oj zm^rJXWyz{nK)NdJ)H@r$%8aEO#7LU>F?~Kvhhnuseg7HWt0SLxPm1s8SMg^fenpQ_# z>9b3d(aJizv@HXbcP35j26RsyH?DW$gqHE+fXQ(vYeve)5%0NFy16@&=p5IR&n8F# zPpS*>(h_AA80bjla;Y9+17=+b@w=~iaMjVhbU zXQD=Sfyh0g!qqJShO9HNwIf@*F}^W@hnZIMKAuGr5!fhtjSQY1nkGzWX>A$bG^RV3 zOJ{-Hu6V9H-_a6H^^8j-dc_3>Z|XdF!__ot(!@zI_*YhKQ>L`GO+L1*WAcPaoo$mN zZIe32PnZ&IJFfM(V~t}cPB@COkN3C!)eCHU#>@DwknO@3Yl`wwU}J< zd`YQs_%}QFPEidg`p{Z++zy@ln)IW5)ATXEoI0~bSA6OdY`#x@sIN(H@~J06ab}Y~ zR6TKy&_u=g>da}>IOiYK2xeN;>GRe0+kEQuPW?#VgZjapIo#5WsRzyB97ar0>quz*Dz%O- z(K-%VzeueUO0*sittV0Ii6vT}WYGNcK5F00N+`U93itLsh|so{QurMz+}8IpD(onw zunQHg7Z(1zl)@fVxUz20OBmZwZB4 z9R-K~J5gcVB^2h}r4-hp!n4A{{iPHhMuo?P!p2ex&!ECjFCn6yE2Z#TR9JP%d&tA< zr4)9e!p*|PpGqmbj|x|dDE5?6xQv6s-iyh_mAnL#D^TIj7Zb>Dmr^L8!ggWd4oAVE zdNnG%a53fLK1ac!`sb+dq;RpJl)~ev@bJZy?k5}t$Hf*@_?d9=tfSz#cm);i7B03q z3j5pR;rCq8gByJ6cP{Qkw%;nH^B(HF;#02`NIoc~vs@KAA6`Tw=PK2vbhz#7CRKTb zoV|4sIs2}oWQy!@n;!Uj}$;=<}C908A((%6g|>n@~>JnJYp z1MwA9xLbgD)l}%mrJZ%db>M;SA9c`%?FGO5!Pyp%in_z4Ur@P{FwFmZF$_G=;e!HW z=HmkSR4{)TIN}VW!;TIq)OjB2>d{J>haEpxL_kiAHavvr`Yee~Uvq{LMgS zsjoKL9Z$nMz+66EH3UB4p(}V<3`35)YK}hswI=cXK{A9QUgzQv3wMs19>$* z66PaahN8}DZDn=%?cK;`jaYNXQbnf+#(Dpw2gOxNeGa}o196@3XAgP!xLY&U-;qx& zY8FI|t#?(LQ|a8;p-4b;JPB|0;hD4@T)u_OoYaR78QmCWYG*7oERxj4K& z&KqOvd-AzRN5W|CN~Ag>iR{==GL_6_B59z#tGP3sLx#rI!#f%S-uOq2W_q9-TaAC_ I&N-rbB^JK`l_Y%KbtznKaWTnPg^? zw(>}U3L=Q0Jc=Sw0YODCsJ+V53Mh(zs3;HlKC7aD0wNY}FCyIkT6^zv&YVorlos*+ zeqZ=8XP>p#UhlQ`K6{_(h|WU4A%rlFj-F|bjvAw9+N1arXIi7@;u9cora@pqAbBUw zbO;AMA-uZIXI5ql#Y|oZf>tT$n$BXn zR~Q(#iGf?P&NQb7n_JcAz_(&!meT zJ;MX(Vm3F>QS9vM%NU|E-J2aGw!?#>C1QjtDh-h|qIPAps=D%`3yr3jNItyYPKxA@ zj5nhq`k!IL8r^q2{zNjqx=HLOn-8zwuwFD8NzrIpM<+z``(lBJnn#;QC(LBUvT6Yk zAk_(h`?>iAqN>V>N>~h+HUPM5|5ZSWCJex$tCI0J6K^*~!bsYy8Vw9KRyCQ)_;J-j z4F}2O16#yG5ls>kj1qd(rRPB=z$D}31HUVab$I~m*|Jzy2C)8E7HeYw>-n-+*9EZt zR2J(C0jw9xV%-?PdbuptErbOZK1g0l)@lRYs=zmtf^SpcYf8c2R^Yo!!QWNjD@(z5 zEAV$p!J8F$Ln-(H1-_WUt_Odl!0SuF4=eDuO2LmQ@MWdo#})XxQt*=sd@F-pn>?ey zx0GUUQQ-Sa!Otr2_e#Of6F7b`Cke7?fw=5^c0h|Mg|U+7$-cO{wwO|QGkJcnLn><} zFDUIuZcz6;pvi1EUnHgpbMYHS(ugxQVn4o0CTR4>CIVpb=;6j&Qe-sEZC>*g)f9&t z7fM+vZ9b*NmXTt_71I_|#)uCfqr(S~ap42Vi0}bqEcgI23VZKAYfwi$BLpn)Vde1#l*P@(XlG|wirT5U2hDb6f;-~{z=dEpYWP03g*bjh+igc zpF|0i5R`JskX1`chOI(UFoqz}4)b4VDE=i(NioUkQj#ua3B$C>=JT~AR>_^o{%92+ zXI&yi=)V3fYbgFH(g6A!`Q&--eKJzaYcd7~D$b3Ds566;G z)nF+keU>1C_oj1K#p*F*2mTD2D1rMKbk~YNr_@gi!_HHD5{8t30>$$#O_1zeGr4|r zDBol%|9Vor(-tJWcXU;3ZFNI9BCO`-ioJ2Qo10^MhMNtixw&$maI+a|Zmww#H;cOF z<|tijo15c%p})DgxhjEvTqpnAyFyN@>+-Nk(2CahiT%a-;41wfj388p| z6q%|$S&KQwR;BVc{b=S{O=Jk0foXXVN1LKLr4y&N@G`LF6EFsRuW?%oajXfhMho@f?BGn6l)qbb5 zYh6#TmOe%V#7C0IGc;sOl=Zrquw0k>;(hn+38G`*-4v{S zsIX|6BLThzO<;KmO0OU*Kx%}iG_NSO=yi-Vsfnt9tN~sDbEP?g1fn^Uu1T{<^}+8F zl+l8&7xg=G8bp<=qRIFm&ar_Bn}bveeK-^rY4#o-8GG4{V>urVcJ?d5znpSuk^6c zNNWjtpr#r=LE60Y>QGg&i%cSO;$1h%-~I_EiR>bi@Cy0ekCKnkll8kCCE<5$5?Yw) z+5|<8J5lVY%8Lu zj(qI&J($Wv3;2k1McQ3Vu?7ty1ta3T&5xwGDLl!h&#WA^lDDGR3DV51&2c$D{x!n7mmNySRtXEc z65zoFf>#d^DLm8|N`M)JT-6lz^)(85$3$WCVO^u+w;9=ikILjml`cqf;cpW^|9>D9 z9%>53B4NX{Y)6E!EDI^lO<|>$r~>pAior&kRy1uF-8QFA@eiO`PE30!zuw zR2v3q6+F2#hDmA!EHv|p0a$1xX5+}yl5f`drGstkScLj)Cluy)V~iq)6;im#&Nn)c zjP40YUNuGlL)!okUha82dIT-XUK|i3GNmr=o*&ady~G)^j|LQ=aZJ=524yunH0VIm zV+sq<035=Wj^X!$NGx|~BdrY)FOE7$qcb456^aRL_fpO#oI;aGz6oiuKk6z>Kk6`>KhrRaHyprW!WMSOL8RfZfXuyWU^UavRPiTSu)uynQWGqY_1m3 z=wk;OCf>t<2T{`W8ymbdS49wUg765@Q7dXs2vLLj26UO9;iR-esMPp$2b}Pbzg#2? zDpDvx3A;gUDR2!#opP&2$WtWbDH0+@%*GHYV&URejZicZ;;IpyNRC8!J0eUFl7h0~ z9fzcLOajO6Ucjjv8F2CjCk3iuXMvLImu6*U}hCJcP=$u<@n-pRIn$w;5JXeR#J z)MQS`Q=Z7JV&1!eE#Ck1baVBggB`6TDU3;`oO%!)N4Qcq@9?v%daFy-8_$;Z>1u{n z?K;j0nq6Goe9mby8nkY7b|q(<31^;Z`h0PmT9~9f@|&?-LSf~2!SR_25Rt^8=k|HH zvs(!d9HRL-c+s_E3X~^~3D$RrhiGNVS2tNYvi(lS5g5lbGQ2>jWt~1QwP?MGYKwQG zBDP~Xc(s||lj6!6N}jzcW*Md^-5Lvd3r$rH!-jGXC{r2W{Nz#}2jcS^7{_4#7-{gXK{uz4sUV zkA1|mx{yx+A*{63X=%7BN^N@GF;rD4o7E1s_yM#5w5uYrS#8l$+(?d|P;Fx6jg~mR z+JNWK;&WGeqY+1Skz&HqFC@OlnkjWiGuFo#f@Vq`(v0=7hoBkj!;NSjiOIsTm`XMhlZ9h3SvVGxg<~c~{8 zIh8shwK#Ry{;AZ$R5F!1AVu!^oOBP@Wy%f>;Bt&7GD8EXdb_>6%dL}%$8wQysZe+_ z1qQpcc;(I@rbbwDznm&e8Jct~T!yB5T1@gungj~aB#@#>wL!f*7r%}arh%@E9*Ajq zx2}8| z;be%$R1v`?J~l;~Q$~${VM-L^Uz{4+m!^hxgGSS}iuX(5RW5Fg-WIAmS$Y!^EL%E5 zAwXz7Y3qojuSzaLWMC8kkQzp=H&RAcG;2w@fX=h6xR|9^_SZc$OBa(>EMs`*O=RA zd)|G$uEltRx9iz!>J8Y1#+wAAlH0DoBbP9Iyouqs1R*)p1j7E1rcy4sky70)+fw8* z6eb$)QjXuGYew=sypY9Tl9EPJH__-%3?qz<>lTyG+tioIAe;KGod4Tpe2At1c=Bm6 z`Ls*{ECTScm;&Iysm0{;F9pC>W{b%OT?&9*QhtM>#pGix32ner4NMtoNG5Q>b~=4g z#coJOaIqTr^dNMYB;RJfM5?LvD%LQL7 zR^Rqvd3i^p31N90HvUOu+VV)&1djpKA|PQ#Kgh3d0FB<;pnVWUp1-35W}BfP>4g0k z+6{{{!Qi2S`yGIU5j~xSRH@@7ovP`T)mJBsCS;;K|8>ukmwZ^i9na4Zl*lKEY=64+QLwF|)QMg(i4kpB^Mo0_ElQqj4<>`>SR%8k|AEHGQMS#VpP4dfJ z?q+Qhf8}5YcIl$S%KUn@iIwas$fuRVSrh`uF9f#Q$OhQ8r7c^a^7b)(Jw|P-vT_j4 zpzlLL;WE3%(e;LUy-2gQXbX?%t}bD2@E$6R1Tel{;_+fLcwx-!ut^QfG8=kie%KyH z)0#x1Cv!|G_6bpDf0go|b>lNVKF2G2p6BkWMn4>|r9u0#-nZst=>d?wlORcRG?+l5 zqe8yos1O!Y-wtv2JEI+?_QJD*Z(WTD-OROv^I3~Mg$-ek{zrnMyZ&G&!vTcf2}@5pMI4`e|0&|M0!~{v4~E-JR(g zUfI!?OAGwf<9}PcB*a(?S_jEHQ;bn-uIe+!-iOvAw~ty&+`i>#o#OUU>%(r}O0=?W zAGHSDzVD-TzuSif|GkaBxA1=ycX9j=*&vPV6XLz@t%e|V6G~Nso@o>IPCm8*<6;wz zoxFEvY&E%5J41PLYt%y5=@aTZIp4`;o}BW#C)MPXcgBO0QzmPuEH=$?nL<4?CbTnA z-d*)t<$WfkOg;NeIF>9qQ}vFwB=zh) z>DYJb1>&n^F3(r@I%7cPKV1nY*NF0HFZ@qc)EGCG{HuHIalO>jq{fb~z&jgt6W7Tw zcvelwcV{ZY#f*==t6L?_7hg@sAv>Ty9RI}9E$YNYz(Xm9X;ck#WN-90W+7@FG4_u9bxV+ zL4+NdC(Ie%ZN)s0Bg)iQxjz#W0U1t35KJh;gnS+F^)U*8uTPBd#pfHMt-jWPM6(S~ z;v;90%oPp`QW+%fXr-bqafbx2a*6Y8wW7%Al44BSszz!UFGG0I%!>@Lu62#=U>>iB zXwfL_gPZzlm}+S~-OPver|s_R3!04G7Gx4V0Ln2F%N3vPZhJlr5@OrW76IQP41BOQ zSiqjG{75_!5YL&Id#E%b!h5kJ>O!YS|1EkNhDLnii0>_ms`$(f^yI#YP_sF1ZR7h` z>&&@XGC!yx)NQZpli;g6b_TX zL`_6C5shgCtZ+ZXI8Iv#YhRDk9b}_@4{$l%PWSuKeTZzvoSK=_9$+dty-qatc9}4{ z@tp2}kIXq9r%`g&98afTkWO=0r^hs>xy)&U%jsdFanSZS9Y9zMJWj0Bf`Cp7S*MpZ zrz4ru%Pyy*iN;~u<5VQ9V?9pm5DMn8|K?hcIUjd8S+5GD*?Pw_r_*6GX}ybyMzS0y zq@)`WM<8R&lLMSi_BdTc);#4%#n~KV&ZjcxgEZ$A%=zao=MNCg6Ftr+fS=EKyZP7` z@yuYJ>9N0v6g~5kuA*zf!#wkRm;Jsd0cNMZx5n0)s@p}sW4$B4&A z0^$T@@|$EE)&^#OK&JkHO#NC0bBtvgp!-mdmLbP7{K1t0&&a}DSw;pK&$N!!IeG-4 zt_`qXOYB1wuG&F9pH4g$7z82@8PJPuQ}bzobGZt9Z5Lf@i=)p`Ez^$O_vV^I6dTXYNMR~ ziC1C{p!a@-@|BAr)gNgicMmcX#mxu+42`%5LM2;?|DF6bot1m(k10hyli zWV(-B_S8eJo1d)2{nOu5_tqHKrKfcbuw_~NHJARPmhl;u@da1L&l1mHcrtpefS~?> zxBx+AJ{J)5IZsdyi05rQu7*CX8Na}cyI?kHoNI~3?>u(Yk^$k5{AwztTLYZ7dYmqy zNZ(5HD6JjLu~*3?uN_P|cbn$?I&*%;<=jFvUoPb=Q@oNy^LwN~v1Ay;Kgw$po(c?u z1R#Jh#y-IoGjt_@ImS*g4e|;BlvhlH+(H257Rw;NR6VMNv<;T>e1wd&&jw=s_cBuY zB8cVnNA(ZLn>9gsYl8CD7!*W4dE-8L*?Dm(9=>rY)45iP*9h~nc-4;+r%Hyh?E)WS zoGEXjWY{w(+MYhqeoemI(;#O`(f&@(+{++uN>hG?Jh!)TzcRKmehTxlFu=qE)_qCh8^YckvjWL+)hPxS&;BDpUxbN%cMr< zYfkT%oW9|5T0(S=^EmnR@SFG^N~sk=b}M}BIGa}lv-z}OcAO?7?h9I=)1^R*Kwql< z2yyyQKp-zgq$Lp6u8B`Y+e`;VOZ!CQN|!cX0OhKC!I&#uH%nIaj20v#1^Jm0B>aY! zC_+z*R_u4UuOJdaJ!ZyU#!b!N@Sz?8tMequzn2qTN=-Xhdct~$YIrUvbuYm@Q z7u}d;HHKIXnbYjyyg{pnsyDP=1*z8ztjIW}ivhj9f(4JX^zTW8RprDDn!C{__-|?U z24z)Q#2DB0(V*b#gMy#u6P#BF=Nq&nAWiHsULsuJ0vx$U*kI5ip{gCKu**IsRXati zc40uZ4ahvHnxMk5ZerCqZQb0J-Z{Xhq)B=H8K06DP|f~~Pszu~3|IP;yoiQeE8Bd*MAq)wOP{v(NIM>jr)Kwc_aPjoMxSlQBv zP%7K(Q}#;Yd$Uhj_UO&7^5n7`GVzP{C@C@w)me1}&t?#g1H9 zf7Jzq#+K8NuNkz+CycNx!M7UQs02R@{n_VVm-hIRw#PRD_E?LeD9vyasWG-4Gn@du zy;Xp(*wMbV&dqzMCf*S=$Q?d|u>NERVbU&!nWwROmRl-%>IUoA??XI(7}Stg%# zwJ@a?e~?-{t+n{0lzN-i;yHOgJ){EU`H8exj{^*;#bqSQZzk7be31MfS`cmZD?sII zt1GWD#xcKD$~zD8Z~(t1b`)NPW)1$+fg{73!qfz)&w{B|HDKVLfRv%g2sO{w&jbIOEmF zEK^r!wNZGm$<_HG&A(Oh|A)(ee98ykh z&x}4b{13W6qQE{rsK)U=HNHq99B+CLx;3Q6QlA?4k{V0R*GknmK}IkBH48aAER#~N z#`(FF`YB?)cyg)7$Jzo)ChWYF+I@vj;M>R=D@+4N8CCmf?x#uaFSy)4Ky*G7;0}Lb z@NO`)Js69Fh1r0QF%U66XEkF)#E4N)$0$=xT2%KE?qM-n44TK$2i-3*m!o+b4g0Ef zIg&ISF}ndd3K085Gl%9K)OnM23L-9S*AlDQ)Y)S(l@&zgeYI^6<^u%2(TrUWl-jT2 zqY~BCe$c9;>kRvLt9~;I|MTduH(8>76MR8oZ{KN|^|XwkHH`f|D^gE;gM9M?D=wJh zkBCdHRZBYmoTy%I)shZ-Q2W1)SS^Ou?uCzS$V+>5O*c(fN{<6q~fr!_R$%s$p`si*Zb-z>A|)*q~I zK4{MuEY99!fhVPCFOkR+Dq*wuN0Z1~EOv=JHjDifjB6gpN9`7TtR^DgG3)3W zv2Qi&sYH=0ZEP3S(|U(oYHu=a5v#x&u68$kJVzXEcIxOFv2S$he~3ZkEBhu#)V~2E z904r*%Z@E-m!S1o!oR_p##j4`PW=|Fo z*ao1~T#S#}tMKs=68aIdj;;~=J}LAa=&*n27y2h8^u08aWMi8>O?LWkGe!nyU&n~l z7PIyQ2>nrNKWWxdNG>ON{$a(acCsbc5+H2HVwjw2_rS-Kq|}e>I=Uw8+wJ;|C*yM{H`?`2BZxUvt^)_Ek;ZH|FvkxNtCbZTQ{sGQ3x+d&>o%*#1 zhLZ_pKj6QP`SL4t(~O(HKy|*MrEzTszzayYH|;vQChR}j^>l_t=UO(N4eH;74LJe+ z0wdBH7vH>w3?k(}Ph{V)XGrBgPRc)F*KPvz(?oT;Q^!}k#;N}lyg{C{rx9cF@0*0O z%!&P9ptFBh()cci+qY5sWZ530cE7{H@_lL#J7PKHvc-2$qx5O;9hmli1}6Fp8^J3~ zD(BSDYll1r82D+kmryz%gwKu2lh7+{c~mT}qBe$kROvD3RH&`bmx&HpZMQXe13GOn6BcZKjAQ!Nl$R z>-RYe#e7G{n$AMDW3aQ>iyx2c&y8d{1798YJ>S)x+AkT3&0Zk%=Pr( z*X%?d|0{ky-DFn${$8DJ@N zXCm->VT!i#p?X6L*^g#+h=6|plfiE)X7l(}LVw5hEK+U^6HDWDYKq&biWI~zk!Wfm zqh*oUOd#Jm(4Fg_qFGCjeM(R!O_7Au!Z(1)6o$JB>3nvOfAev;W4Oaj3!M`_PypFQ z?aL0VPUnUPir(nVNXCDwiMd1ESoI(>?A-f&c4cp|5TKY})6>_9V05v13dI1VFPB?A zJQx7xa{)l`9;&f{0%(P70FVy?3f%#W9v3i-GBk)v?+UA}fI{y9$s#@h z-ffB5U1B9rtroH>XElJ#+ z=__`67{i53zPpnlaYP8Q&jYAJX1M~`lO6zdJwL8kT3}gDCRvE!ln8T>r zml=ix?e9o;_Vsmi7jyZ781TEiAYfn)Jxpno6G8=^KuUB-h&RFc$m_1{mCsV46pMnQ zjFpRoK zAU-_|9n=`etdXl!x}oS`RJ!RJ z?qQ63q?qaJn}XJ`CS&GDI{U-`O;nl& zi&I$FuVC9thX6V|mSN=V9iidF_lWEW0lyMZuaL=*PTP}TV{*xKo#Q&jTZ{MQ2398M zqZEX)q6!**EV?&?IU@^{V&_VJD(HYug}9fb>ulWE=&i*@$i@Cwp-p;KXJgJ1wR_pet7_=K3WKWZp352hb)?vQZ?!8N44c zHG<W4%|B#bPy77N_IX`%4>=;;et6sAhh+{s^pFFE_{VPgTQ8XQGQXm| zLh&+ftQp4NW7Tv4hz>lB)B@>8ICqZ9CC{;wDU5_d+z2V6%_KzmM+;Z5nXn-)Qq*;6(U;uV>5T zmw0c3@;zqHGgL6H$5$NqyrZKRGfyVpAL(23nMDH=&dsLa;2L-ynJIehJi82VkB!sVy z-btc8Sq|a1gmC@nuL$AUatJRI!WELjU&|qEBZP}a&nF6RltVZV|3r@U*13#ufl)@t zO9)}>xva%y9zxhP??ys+P9l8PL-6c>6Cpf(E~|M;;wQC_YuN9=dimU z@DMx}4->+-B#TEq1dqiNgm8;w@wA7qn;Q?mHw~xuu8{S)b9%_y&y~Y@m2jR2S(i#l zUN48U!IC&{oXwGQvE`a{rpxPU%XosBJ$E)UyUs)M1iDob`uW-Ho7>Cb+(kGL`hLl5 zb2*$JE1XS|*CQT|C(y4f<1Qw2vqXBP9MT^wV~mkDN~Aw~NS@SN6}L}FoNXSC=Zg(C zeDU|QIHE4HUHe$BeJ-;NvocumuBK5hoW%y)=#BEojuFC^vpCGYR1V=*Lioj5?3e#2 zhj14mJaASdK@sr1au^R0#-_8_BaeCrULZa}2)9Tfo>Bc(LQag^u|*+*=13D;;B{ z56Y`NK>OIu;(AN}s{}X#TA3-(KezM$|9|&wfN0=9dQIT3_@CYj5Gf(F?Nv8klCW*w zr>Ka0bpG7g^h{v<#yU{cjI8Y#%nsnGYGt-i%;1X%3b3ZLnC>;HoA$x;9rC!{in#!s zDCCQ_i%y3#j;2QY-2K>y2t{4;o!#L&UQ_jBUoMMoyi1T@7pYA5W(To1SR5X#h~lUd z1L>$DQyIZ2D&I##XbtpdtLTbTj%w=R-N9IBaJbOh)?O01!*y z^I5#FfF1FCoodsAd3{Z0Z6-a8!=&s$H=5nkDm5V8wOf^L_jhIordQBb18?*0UO`-V zb8Lp49mvn0X?Lgd^Y?HDbAxT|4NkVdzpZ`G5SVrJ_U#q!?(FPF9Da;m@h&|+-)PhznsDgc(UZYB75rW?E7~>Ox4Kn^$*daR zO>1tjIBPl?pcVeh;$WcIiW9Uv?FG+@W!4t4y9J@sxq(7)R%QQiUoneK_2JB{n*QNp zXIEdQb!A_!tFx~#t9~FiP|SA@A^}#m_6!!uLbGbHbCtn4NIKKXkGZoV^zuh5=>891 Cd$#ZZ literal 0 HcmV?d00001 diff --git a/test/test_custom_chips/sdk/examples/z80-boot-rom.c b/test/test_custom_chips/sdk/examples/z80-boot-rom.c new file mode 100644 index 00000000..1730833c --- /dev/null +++ b/test/test_custom_chips/sdk/examples/z80-boot-rom.c @@ -0,0 +1,104 @@ +/* + * z80-boot-rom — a rom-32k variant whose image is a tiny Z80 boot program, + * for the Phase 0-2 live proof (project/multichip-bus/). Identical to + * examples/intel/rom-32k.c except for rom_image, which holds: + * + * 0000: C3 06 00 JP 0x0006 ; requires reading C3,06,00 over the bus + * 0003: 00 00 00 NOP x3 ; jumped over + * 0006: 76 HALT ; Z80 drives HALT low here -> observable + * + * Reaching HALT proves the Z80 fetched the multi-byte JP and its target from + * this ROM across the shared data bus and executed it. + */ +#include "velxio-chip.h" +#include +#include + +#define ROM_SIZE 0x8000 /* 32 KB */ + +static const uint8_t rom_image[ROM_SIZE] = { + [0x0000] = 0xC3, [0x0001] = 0x06, [0x0002] = 0x00, /* JP 0x0006 */ + [0x0003] = 0x00, [0x0004] = 0x00, [0x0005] = 0x00, /* NOP NOP NOP */ + [0x0006] = 0x76, /* HALT */ +}; + +typedef struct { + vx_pin a[15]; + vx_pin d[8]; + vx_pin ce; + vx_pin oe; + vx_pin vcc; + vx_pin gnd; + bool driving; +} chip_t; + +static chip_t G; + +static uint16_t read_addr(void) { + uint16_t v = 0; + for (int i = 0; i < 15; i++) if (vx_pin_read(G.a[i])) v |= (1u << i); + return v; +} + +static uint8_t image_byte(uint16_t addr) { + if (addr >= ROM_SIZE) return 0xFF; + /* honour the programmed low bytes; an erased EPROM reads 0xFF elsewhere */ + if (addr >= 0x10) return 0xFF; + return rom_image[addr]; +} + +static void drive_data(uint8_t v) { + for (int i = 0; i < 8; i++) { + vx_pin_set_mode(G.d[i], VX_OUTPUT); + vx_pin_write(G.d[i], (v >> i) & 1); + } + G.driving = true; +} + +static void release_data(void) { + if (!G.driving) return; + for (int i = 0; i < 8; i++) vx_pin_set_mode(G.d[i], VX_INPUT); + G.driving = false; +} + +static void update_outputs(void) { + int ce_low = (vx_pin_read(G.ce) == 0); + int oe_low = (vx_pin_read(G.oe) == 0); + if (ce_low && oe_low) { + drive_data(image_byte(read_addr())); + } else { + release_data(); + } +} + +static void on_pin_change(void* user_data, vx_pin pin, int value) { + (void)user_data; (void)pin; (void)value; + update_outputs(); +} + +void chip_setup(void) { + char name[4]; + for (int i = 0; i < 15; i++) { + name[0]='A'; + if (i<10) { name[1]='0'+i; name[2]=0; } + else { name[1]='1'; name[2]='0'+(i-10); name[3]=0; } + G.a[i] = vx_pin_register(name, VX_INPUT); + } + for (int i = 0; i < 8; i++) { + name[0]='D'; name[1]='0'+i; name[2]=0; + G.d[i] = vx_pin_register(name, VX_INPUT); + } + G.ce = vx_pin_register("CE", VX_INPUT); + G.oe = vx_pin_register("OE", VX_INPUT); + G.vcc = vx_pin_register("VCC", VX_INPUT); + G.gnd = vx_pin_register("GND", VX_INPUT); + G.driving = false; + + for (int i = 0; i < 15; i++) { + vx_pin_watch(G.a[i], VX_EDGE_BOTH, on_pin_change, 0); + } + vx_pin_watch(G.ce, VX_EDGE_BOTH, on_pin_change, 0); + vx_pin_watch(G.oe, VX_EDGE_BOTH, on_pin_change, 0); + + update_outputs(); +} diff --git a/test/test_custom_chips/sdk/examples/z80-boot-rom.chip.json b/test/test_custom_chips/sdk/examples/z80-boot-rom.chip.json new file mode 100644 index 00000000..f2429a6e --- /dev/null +++ b/test/test_custom_chips/sdk/examples/z80-boot-rom.chip.json @@ -0,0 +1,9 @@ +{ + "schema": "velxio-chip/v1", + "name": "Z80 Boot ROM (JP+HALT)", + "author": "Velxio", + "license": "MIT", + "description": "A rom-32k variant holding a tiny Z80 boot program (JP 0x0006; HALT). Fixture for the Phase 0-2 live proof that a real Z80 boots from a ROM over a chip-to-chip bus.", + "pins": ["A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "A11", "A12", "A13", "A14", "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "CE", "OE", "VCC", "GND"], + "attributes": [] +}