pico-emu 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +95 -0
  3. package/adc_rv32.uf2 +0 -0
  4. package/adc_test.uf2 +0 -0
  5. package/adc_test_pico2.uf2 +0 -0
  6. package/ble_adv.uf2 +0 -0
  7. package/ble_adv_pico2.uf2 +0 -0
  8. package/cli.js +247 -0
  9. package/clocks_rv32.uf2 +0 -0
  10. package/clocks_test.uf2 +0 -0
  11. package/clocks_test_pico2.uf2 +0 -0
  12. package/dma_rv32.uf2 +0 -0
  13. package/dma_test.uf2 +0 -0
  14. package/dma_test_pico2.uf2 +0 -0
  15. package/dualcore_rv32.uf2 +0 -0
  16. package/eth_dhcp.uf2 +0 -0
  17. package/eth_dhcp6300.uf2 +0 -0
  18. package/eth_dhcp6300_pico2.uf2 +0 -0
  19. package/eth_dhcp6300_rv32.uf2 +0 -0
  20. package/eth_dhcp_pico2.uf2 +0 -0
  21. package/eth_dhcp_rv32.uf2 +0 -0
  22. package/eth_http.uf2 +0 -0
  23. package/eth_http6300.uf2 +0 -0
  24. package/eth_http6300_pico2.uf2 +0 -0
  25. package/eth_http6300_rv32.uf2 +0 -0
  26. package/eth_http_pico2.uf2 +0 -0
  27. package/eth_http_rv32.uf2 +0 -0
  28. package/ethdhcp6300_arduino.uf2 +0 -0
  29. package/ethdhcp6300_arduino_pico2.uf2 +0 -0
  30. package/examples/adc_rv32.uf2 +0 -0
  31. package/examples/adc_test.uf2 +0 -0
  32. package/examples/adc_test_pico2.uf2 +0 -0
  33. package/examples/ble_adv.uf2 +0 -0
  34. package/examples/ble_adv_pico2.uf2 +0 -0
  35. package/examples/clocks_rv32.uf2 +0 -0
  36. package/examples/clocks_test.uf2 +0 -0
  37. package/examples/clocks_test_pico2.uf2 +0 -0
  38. package/examples/dma_rv32.uf2 +0 -0
  39. package/examples/dma_test.uf2 +0 -0
  40. package/examples/dma_test_pico2.uf2 +0 -0
  41. package/examples/dualcore_rv32.uf2 +0 -0
  42. package/examples/eth_dhcp.uf2 +0 -0
  43. package/examples/eth_dhcp6300.uf2 +0 -0
  44. package/examples/eth_dhcp6300_pico2.uf2 +0 -0
  45. package/examples/eth_dhcp6300_rv32.uf2 +0 -0
  46. package/examples/eth_dhcp_pico2.uf2 +0 -0
  47. package/examples/eth_dhcp_rv32.uf2 +0 -0
  48. package/examples/eth_http.uf2 +0 -0
  49. package/examples/eth_http6300.uf2 +0 -0
  50. package/examples/eth_http6300_pico2.uf2 +0 -0
  51. package/examples/eth_http6300_rv32.uf2 +0 -0
  52. package/examples/eth_http_pico2.uf2 +0 -0
  53. package/examples/eth_http_rv32.uf2 +0 -0
  54. package/examples/fp_test.uf2 +0 -0
  55. package/examples/fp_test_pico2.uf2 +0 -0
  56. package/examples/gpio_rv32.uf2 +0 -0
  57. package/examples/gpio_test.uf2 +0 -0
  58. package/examples/gpio_test_pico2.uf2 +0 -0
  59. package/examples/hello_rv32.uf2 +0 -0
  60. package/examples/hello_usb.uf2 +0 -0
  61. package/examples/hello_world.uf2 +0 -0
  62. package/examples/hello_world_pico2.uf2 +0 -0
  63. package/examples/i2c_rv32.uf2 +0 -0
  64. package/examples/i2c_test.uf2 +0 -0
  65. package/examples/i2c_test_pico2.uf2 +0 -0
  66. package/examples/interrupt_rv32.uf2 +0 -0
  67. package/examples/interrupt_test.uf2 +0 -0
  68. package/examples/interrupt_test_pico2.uf2 +0 -0
  69. package/examples/littleos.uf2 +0 -0
  70. package/examples/littleos_pico2.uf2 +0 -0
  71. package/examples/littleos_pico2_riscv.uf2 +0 -0
  72. package/examples/micropython_rp2040.uf2 +0 -0
  73. package/examples/micropython_rp2350.uf2 +0 -0
  74. package/examples/name_prompt.uf2 +0 -0
  75. package/examples/name_prompt_pico2.uf2 +0 -0
  76. package/examples/name_prompt_rv32.uf2 +0 -0
  77. package/examples/pio_rv32.uf2 +0 -0
  78. package/examples/pio_test.uf2 +0 -0
  79. package/examples/pio_test_pico2.uf2 +0 -0
  80. package/examples/psm_rv32.uf2 +0 -0
  81. package/examples/psm_test.uf2 +0 -0
  82. package/examples/psm_test_pico2.uf2 +0 -0
  83. package/examples/pwm_rv32.uf2 +0 -0
  84. package/examples/pwm_test.uf2 +0 -0
  85. package/examples/pwm_test_pico2.uf2 +0 -0
  86. package/examples/rtc_rv32.uf2 +0 -0
  87. package/examples/rtc_test.uf2 +0 -0
  88. package/examples/rtc_test_pico2.uf2 +0 -0
  89. package/examples/spi_rv32.uf2 +0 -0
  90. package/examples/spi_test.uf2 +0 -0
  91. package/examples/spi_test_pico2.uf2 +0 -0
  92. package/examples/timer_rv32.uf2 +0 -0
  93. package/examples/timer_test.uf2 +0 -0
  94. package/examples/timer_test_pico2.uf2 +0 -0
  95. package/examples/uart_echo.uf2 +0 -0
  96. package/examples/uart_echo_pico2.uf2 +0 -0
  97. package/examples/uart_echo_rv32.uf2 +0 -0
  98. package/examples/usb_rv32.uf2 +0 -0
  99. package/examples/usb_test.uf2 +0 -0
  100. package/examples/usb_test_pico2.uf2 +0 -0
  101. package/examples/wifi_ble_adv_rv32.uf2 +0 -0
  102. package/examples/wifi_ble_gatt_rv32.uf2 +0 -0
  103. package/examples/wifi_join_rv32.uf2 +0 -0
  104. package/examples/wifi_ping.uf2 +0 -0
  105. package/examples/wifi_ping6_rv32.uf2 +0 -0
  106. package/examples/wifi_ping_pico2.uf2 +0 -0
  107. package/examples/wifi_ping_rv32.uf2 +0 -0
  108. package/examples/wifi_rv32.uf2 +0 -0
  109. package/examples/wifi_scan.uf2 +0 -0
  110. package/examples/wifi_scan_pico2.uf2 +0 -0
  111. package/examples/wifi_scan_rv32.uf2 +0 -0
  112. package/examples/wifi_webserver.uf2 +0 -0
  113. package/examples/wifi_webserver_pico2.uf2 +0 -0
  114. package/examples/wifi_webserver_rv32.uf2 +0 -0
  115. package/examples/ws2812_rv32.uf2 +0 -0
  116. package/examples/ws2812_test.uf2 +0 -0
  117. package/examples/ws2812_test_pico2.uf2 +0 -0
  118. package/fp_test.uf2 +0 -0
  119. package/fp_test_pico2.uf2 +0 -0
  120. package/gpio_rv32.uf2 +0 -0
  121. package/gpio_test.uf2 +0 -0
  122. package/gpio_test_pico2.uf2 +0 -0
  123. package/hello_rv32.uf2 +0 -0
  124. package/hello_usb.uf2 +0 -0
  125. package/hello_world.uf2 +0 -0
  126. package/hello_world_pico2.uf2 +0 -0
  127. package/i2c_rv32.uf2 +0 -0
  128. package/i2c_test.uf2 +0 -0
  129. package/i2c_test_pico2.uf2 +0 -0
  130. package/index.html +1055 -0
  131. package/interrupt_rv32.uf2 +0 -0
  132. package/interrupt_test.uf2 +0 -0
  133. package/interrupt_test_pico2.uf2 +0 -0
  134. package/littleos.uf2 +0 -0
  135. package/littleos_pico2.uf2 +0 -0
  136. package/littleos_pico2_riscv.uf2 +0 -0
  137. package/micropython_rp2040.uf2 +0 -0
  138. package/micropython_rp2350.uf2 +0 -0
  139. package/name_prompt.uf2 +0 -0
  140. package/name_prompt_pico2.uf2 +0 -0
  141. package/name_prompt_rv32.uf2 +0 -0
  142. package/package.json +27 -0
  143. package/picoemu.wasm.js +2 -0
  144. package/picoemu.wasm.threads.js +2 -0
  145. package/picoemu.wasm.threads.wasm +0 -0
  146. package/picoemu.wasm.wasm +0 -0
  147. package/picoemu_worker.js +81 -0
  148. package/pio_rv32.uf2 +0 -0
  149. package/pio_test.uf2 +0 -0
  150. package/pio_test_pico2.uf2 +0 -0
  151. package/psm_rv32.uf2 +0 -0
  152. package/psm_test.uf2 +0 -0
  153. package/psm_test_pico2.uf2 +0 -0
  154. package/pwm_rv32.uf2 +0 -0
  155. package/pwm_test.uf2 +0 -0
  156. package/pwm_test_pico2.uf2 +0 -0
  157. package/rtc_rv32.uf2 +0 -0
  158. package/rtc_test.uf2 +0 -0
  159. package/rtc_test_pico2.uf2 +0 -0
  160. package/spi_rv32.uf2 +0 -0
  161. package/spi_test.uf2 +0 -0
  162. package/spi_test_pico2.uf2 +0 -0
  163. package/timer_rv32.uf2 +0 -0
  164. package/timer_test.uf2 +0 -0
  165. package/timer_test_pico2.uf2 +0 -0
  166. package/uart_echo.uf2 +0 -0
  167. package/uart_echo_pico2.uf2 +0 -0
  168. package/uart_echo_rv32.uf2 +0 -0
  169. package/usb_rv32.uf2 +0 -0
  170. package/usb_test.uf2 +0 -0
  171. package/usb_test_pico2.uf2 +0 -0
  172. package/wifi_ble_adv_rv32.uf2 +0 -0
  173. package/wifi_ble_gatt_rv32.uf2 +0 -0
  174. package/wifi_join_rv32.uf2 +0 -0
  175. package/wifi_ping.uf2 +0 -0
  176. package/wifi_ping6_rv32.uf2 +0 -0
  177. package/wifi_ping_pico2.uf2 +0 -0
  178. package/wifi_ping_rv32.uf2 +0 -0
  179. package/wifi_rv32.uf2 +0 -0
  180. package/wifi_scan.uf2 +0 -0
  181. package/wifi_scan_pico2.uf2 +0 -0
  182. package/wifi_scan_rv32.uf2 +0 -0
  183. package/wifi_webserver.uf2 +0 -0
  184. package/wifi_webserver_pico2.uf2 +0 -0
  185. package/wifi_webserver_rv32.uf2 +0 -0
  186. package/ws2812_rv32.uf2 +0 -0
  187. package/ws2812_test.uf2 +0 -0
  188. package/ws2812_test_pico2.uf2 +0 -0
package/index.html ADDED
@@ -0,0 +1,1055 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Pico-emu WASM - RP2350 Emulator</title>
7
+ <meta name="description" content="Pico-emu: a browser RP2040/RP2350 emulator. Load real UF2 firmware and watch it run.">
8
+ <link rel="icon" type="image/png" href="./picoemu-logo.png">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
12
+ <link rel="stylesheet" href="./site.css">
13
+ </head>
14
+ <body>
15
+
16
+ <header class="faceplate">
17
+ <a class="brand" href="./index.html" aria-label="Pico-emu home">
18
+ <span class="brand-mark" aria-hidden="true"></span>
19
+ <span>
20
+ <span class="brand-name">Pico-emu</span><br>
21
+ <span class="brand-sub">RP2040 / RP2350 emulator in the browser</span>
22
+ </span>
23
+ </a>
24
+ <nav class="site-nav" aria-label="Site">
25
+ <a href="./index.html" aria-current="page">Bench</a>
26
+ <a href="./about.html">About</a>
27
+ <a href="./docs.html">Docs</a>
28
+ <a href="https://github.com/danish9661/pico-emulator">GitHub</a>
29
+ </nav>
30
+ <span class="status-cluster"><span class="status-led" id="run-led" aria-hidden="true"></span><span class="status" id="status">Loading…</span></span>
31
+ <span class="status" id="perf-info"></span>
32
+ </header>
33
+
34
+ <div class="bench">
35
+ <div class="rail">
36
+ <div class="panel">
37
+ <h3>Firmware</h3>
38
+ <div class="drop-zone" id="drop-zone">
39
+ Drop UF2 or ELF here<br>or click to select
40
+ <input type="file" id="file-input" accept=".uf2,.elf,.bin" style="display:none">
41
+ </div>
42
+ <div class="info-row"><span>File:</span><span class="info-val" id="fw-name">none</span></div>
43
+ <p style="font-size:11px;opacity:.75">W6300 Ethernet (dual IPv4/IPv6 TCP/IP offload: TCP6/UDP6/TCPD/UDPD, CONNECT6/SEND6, masked INT, retry engine) is fully modeled — pick any <code>pico-w6300</code> Example with the <code>pico-w6300</code> board. Full feature list on the <a href="./docs.html">docs page</a>.</p>
44
+ </div>
45
+
46
+ <div class="panel">
47
+ <h2>Examples</h2>
48
+ <div style="display:flex;flex-direction:column;gap:8px">
49
+ <label>RP2040 (M0+)
50
+ <select id="demo-rp2040" onchange="demoPicked(this)">
51
+ <option value="">Pick a demo…</option>
52
+ <option value="hello_world.uf2">hello_world</option>
53
+ <option value="gpio_test.uf2">gpio_test</option>
54
+ <option value="timer_test.uf2">timer</option>
55
+ <option value="interrupt_test.uf2">interrupt</option>
56
+ <option value="name_prompt.uf2">name_prompt</option>
57
+ <option value="littleos.uf2">littleOS</option>
58
+ <option value="micropython_rp2040.uf2">MicroPython</option>
59
+ <option value="spi_test.uf2">SPI 0x4003C000</option>
60
+ <option value="i2c_test.uf2">I2C 0x40044000</option>
61
+ <option value="pwm_test.uf2">PWM</option>
62
+ <option value="adc_test.uf2">ADC</option>
63
+ <option value="dma_test.uf2">DMA</option>
64
+ <option value="pio_test.uf2">PIO</option>
65
+ <option value="usb_test.uf2">USB</option>
66
+ <option value="hello_usb.uf2">hello_usb CDC</option>
67
+ <option value="clocks_test.uf2">Clocks</option>
68
+ <option value="psm_test.uf2">PSM</option>
69
+ <option value="fp_test.uf2">FP dbl</option>
70
+ <option value="ws2812_test.uf2">WS2812</option>
71
+ <option value="rtc_test.uf2">RTC</option>
72
+ <option value="uart_echo.uf2">UART echo</option>
73
+ <option value="wifi_scan.uf2">WiFi scan</option>
74
+ <option value="wifi_ping.uf2">WiFi ping</option>
75
+ <option value="wifi_webserver.uf2">WiFi webserver</option>
76
+ <option value="eth_dhcp.uf2">ETH DHCP (pico-eth)</option>
77
+ <option value="eth_dhcp6300.uf2">ETH DHCP (pico-w6300)</option>
78
+ <option value="ethdhcp6300_arduino.uf2">ETH DHCP Arduino (pico-w6300)</option>
79
+ <option value="eth_http.uf2">ETH HTTP (pico-eth)</option>
80
+ <option value="eth_http6300.uf2">ETH HTTP (pico-w6300)</option>
81
+ <option value="ble_adv.uf2">BLE advertise M0+ (CYW43)</option>
82
+ </select>
83
+ </label>
84
+ <label>RP2350 M33 (ARM)
85
+ <select id="demo-m33" onchange="demoPicked(this)">
86
+ <option value="">Pick a demo…</option>
87
+ <option value="littleos_pico2.uf2">littleOS M33</option>
88
+ <option value="hello_world_pico2.uf2">Hello M33</option>
89
+ <option value="gpio_test_pico2.uf2">GPIO M33</option>
90
+ <option value="timer_test_pico2.uf2">Timer M33</option>
91
+ <option value="micropython_rp2350.uf2">MicroPython</option>
92
+ <option value="spi_test_pico2.uf2">SPI M33</option>
93
+ <option value="i2c_test_pico2.uf2">I2C M33</option>
94
+ <option value="pwm_test_pico2.uf2">PWM M33</option>
95
+ <option value="adc_test_pico2.uf2">ADC M33</option>
96
+ <option value="dma_test_pico2.uf2">DMA M33</option>
97
+ <option value="pio_test_pico2.uf2">PIO M33</option>
98
+ <option value="usb_test_pico2.uf2">USB M33</option>
99
+ <option value="clocks_test_pico2.uf2">Clocks M33</option>
100
+ <option value="psm_test_pico2.uf2">PSM M33</option>
101
+ <option value="fp_test_pico2.uf2">FP M33</option>
102
+ <option value="ws2812_test_pico2.uf2">WS2812 M33</option>
103
+ <option value="rtc_test_pico2.uf2">RTC M33</option>
104
+ <option value="uart_echo_pico2.uf2">Echo M33</option>
105
+ <option value="interrupt_test_pico2.uf2">IRQ M33</option>
106
+ <option value="name_prompt_pico2.uf2">Name M33</option>
107
+ <option value="wifi_scan_pico2.uf2">WiFi scan M33</option>
108
+ <option value="wifi_ping_pico2.uf2">WiFi ping M33</option>
109
+ <option value="wifi_webserver_pico2.uf2">WiFi webserver M33</option>
110
+ <option value="eth_dhcp_pico2.uf2">ETH DHCP M33 (pico-eth2)</option>
111
+ <option value="eth_dhcp6300_pico2.uf2">ETH DHCP M33 (pico-w6300-2)</option>
112
+ <option value="ethdhcp6300_arduino_pico2.uf2">ETH DHCP Arduino M33 (pico-w6300-2)</option>
113
+ <option value="eth_http_pico2.uf2">ETH HTTP M33 (pico-eth2)</option>
114
+ <option value="eth_http6300_pico2.uf2">ETH HTTP M33 (pico-w6300-2)</option>
115
+ <option value="ble_adv_pico2.uf2">BLE advertise M33 (CYW43)</option>
116
+ </select>
117
+ </label>
118
+ <label>RP2350 Hazard3 (RISC-V)
119
+ <select id="demo-rv32" onchange="demoPicked(this)">
120
+ <option value="">Pick a demo…</option>
121
+ <option value="littleos_pico2_riscv.uf2">littleOS RV32</option>
122
+ <option value="hello_rv32.uf2">Hello RV32</option>
123
+ <option value="gpio_rv32.uf2">GPIO RV32</option>
124
+ <option value="timer_rv32.uf2">Timer RV32</option>
125
+ <option value="interrupt_rv32.uf2">IRQ RV32</option>
126
+ <option value="dualcore_rv32.uf2">Dual RV32</option>
127
+ <option value="spi_rv32.uf2">SPI RV32</option>
128
+ <option value="i2c_rv32.uf2">I2C RV32</option>
129
+ <option value="pwm_rv32.uf2">PWM RV32</option>
130
+ <option value="adc_rv32.uf2">ADC RV32</option>
131
+ <option value="dma_rv32.uf2">DMA RV32</option>
132
+ <option value="pio_rv32.uf2">PIO RV32</option>
133
+ <option value="usb_rv32.uf2">USB RV32</option>
134
+ <option value="clocks_rv32.uf2">Clocks RV32</option>
135
+ <option value="psm_rv32.uf2">PSM RV32</option>
136
+ <option value="ws2812_rv32.uf2">WS2812 RV32</option>
137
+ <option value="rtc_rv32.uf2">RTC RV32</option>
138
+ <option value="uart_echo_rv32.uf2">Echo RV32</option>
139
+ <option value="name_prompt_rv32.uf2">Name RV32</option>
140
+ <option value="wifi_rv32.uf2">WiFi SPI RV32</option>
141
+ <option value="wifi_scan_rv32.uf2">WiFi scan RV32</option>
142
+ <option value="wifi_ping_rv32.uf2">WiFi ping RV32</option>
143
+ <option value="wifi_join_rv32.uf2">WiFi join RV32</option>
144
+ <option value="wifi_ping6_rv32.uf2">WiFi ping6 RV32</option>
145
+ <option value="wifi_webserver_rv32.uf2">WiFi webserver RV32</option>
146
+ <option value="wifi_ble_adv_rv32.uf2">BLE advertise RV32</option>
147
+ <option value="wifi_ble_gatt_rv32.uf2">BLE GATT RV32</option>
148
+ <option value="eth_dhcp_rv32.uf2">ETH DHCP RV32 (pico-eth)</option>
149
+ <option value="eth_dhcp6300_rv32.uf2">ETH DHCP RV32 (pico-w6300)</option>
150
+ <option value="eth_http_rv32.uf2">ETH HTTP RV32 (pico-eth)</option>
151
+ <option value="eth_http6300_rv32.uf2">ETH HTTP RV32 (pico-w6300)</option>
152
+ </select>
153
+ </label>
154
+ </div>
155
+ </div>
156
+
157
+ <div class="panel">
158
+ <h2>Run controls</h2>
159
+ <div class="btn-row">
160
+ <button class="btn" id="btn-run">Run</button>
161
+ <button class="btn" id="btn-pause">Pause</button>
162
+ <button class="btn" id="btn-reset">Reset</button>
163
+ </div>
164
+ <div style="margin-top:8px">
165
+ <label>Clock
166
+ <select id="clock-select">
167
+ <option value="1">1 MHz</option>
168
+ <option value="12">12 MHz</option>
169
+ <option value="50">50 MHz</option>
170
+ <option value="125" selected>125 MHz</option>
171
+ <option value="150">150 MHz</option>
172
+ <option value="250">250 MHz</option>
173
+ </select>
174
+ </label>
175
+ </div>
176
+ <div style="margin-top:8px">
177
+ <label>Cores <select id="cores-select"><option value="1">1</option><option value="2" selected>2</option></select></label>
178
+ <label style="margin-left:8px"><input type="checkbox" id="chk-jit"> JIT</label>
179
+ <label style="margin-left:8px"><input type="checkbox" id="chk-debug"> Debug</label>
180
+ </div>
181
+ <div style="margin-top:8px">
182
+ <label>GDB WebSocket <input id="gdb-port" placeholder="3333" style="width:56px"> <button class="btn" onclick="connectGDB()">Connect</button></label>
183
+ </div>
184
+ <div style="margin-top:8px">
185
+ <label>Flash image <input id="flash-file" type="file" style="width:150px"></label>
186
+ </div>
187
+ <div style="margin-top:8px">
188
+ <label>Net <input id="net-url" placeholder="ws://localhost:9999" style="width:130px"> <button class="btn" onclick="connectNet()">Net</button></label>
189
+ </div>
190
+ <div style="margin-top:8px">
191
+ <label>Gateway <input id="gw-url" placeholder="ws://localhost:5090/api/network-gateway" style="width:150px" value="ws://localhost:5090/api/network-gateway"></label><br>
192
+ <label>Room <input id="gw-room" placeholder="(isolated)" style="width:100px"></label>
193
+ <button class="btn" onclick="connectGateway()">Go</button>
194
+ <span id="gw-status">off</span>
195
+ </div>
196
+ <div style="margin-top:8px">
197
+ <label>BLE HCI <input id="ble-url" placeholder="ws://localhost:5090/api/ble-gateway" style="width:150px" value="ws://localhost:5090/api/ble-gateway"></label><br>
198
+ <button class="btn" onclick="connectBleHci()">Go</button>
199
+ <span id="ble-status">off</span>
200
+ </div>
201
+ <div style="margin-top:8px">
202
+ <label>Board <select id="board-select"><option value="">none</option><option value="pico-eth">pico-eth (W5500-EVB-Pico)</option><option value="pico-eth2">pico-eth2 (W5500-EVB-Pico2)</option><option value="pico-w6300">pico-w6300 (W6300-EVB-Pico)</option><option value="pico-w6300-2">pico-w6300-2 (W6300-EVB-Pico2)</option></select></label>
203
+ <label style="margin-left:8px">SPI <select id="board-spi"><option value="0">0</option><option value="1">1</option></select></label>
204
+ <label style="margin-left:8px"><input type="checkbox" id="chk-board-live"> live</label>
205
+ </div>
206
+ <div style="margin-top:8px">
207
+ <label><input type="checkbox" id="chk-eth-gw" checked> Ethernet via gateway</label>
208
+ <span style="font-size:11px;opacity:.75">single gateway: eth MACRAW frames join the CYW43 vnet bus</span>
209
+ </div>
210
+ </div>
211
+
212
+ <div class="panel">
213
+ <h2>Core state</h2>
214
+ <div class="info-row"><span>PC:</span><span class="info-val" id="core-pc">0x00000000</span></div>
215
+ <div class="info-row"><span>SP:</span><span class="info-val" id="core-sp">0x00000000</span></div>
216
+ <div class="info-row"><span>Status:</span><span class="info-val" id="core-status">idle</span></div>
217
+ <div class="info-row"><span>Speed:</span><span class="info-val" id="core-mips">0</span></div>
218
+ <div class="info-row"><span>Realtime:</span><span class="info-val" id="core-speed">-</span></div>
219
+ </div>
220
+
221
+ <div class="panel">
222
+ <h2>Inspect memory</h2>
223
+ <div class="btn-row">
224
+ <input type="text" id="mem-addr" placeholder="0x10000000"
225
+ style="width:110px">
226
+ <button class="btn" id="btn-read-mem">Read32</button>
227
+ <span class="info-val" id="mem-val" style="line-height:30px">-</span>
228
+ </div>
229
+ </div>
230
+
231
+ <div class="panel">
232
+ <h2>Devtools</h2>
233
+ <p style="margin-bottom:8px">Eighteen instruments from the native build — coverage, trace, hotspots, profile, callgraph, VCD, IRQ latency, stack check, heatmap and more.</p>
234
+ <div class="btn-row">
235
+ <button class="btn" onclick="devStart('coverage')">Coverage</button>
236
+ <button class="btn" onclick="devStart('trace')">Trace</button>
237
+ <button class="btn" onclick="devStart('hotspots')">Hotspots</button>
238
+ <button class="btn" onclick="devStart('profile')">Profile</button>
239
+ <button class="btn" onclick="devStart('callgraph')">Callgraph</button>
240
+ <button class="btn" onclick="devStart('vcd')">VCD</button>
241
+ <button class="btn" onclick="devStart('irq')">IRQ latency</button>
242
+ <button class="btn" onclick="devStart('stack')">Stack</button>
243
+ <button class="btn" onclick="devStart('heatmap')">Heatmap</button>
244
+ </div>
245
+ <div class="btn-row" style="margin-top:8px">
246
+ <button class="btn primary" onclick="devDump()">Dump + download</button>
247
+ <span class="info-val" id="dev-status">idle</span>
248
+ </div>
249
+ <p style="margin-top:8px">Proxy helper: <code>python3 web/net_proxy.py --ws 8765</code></p>
250
+ </div>
251
+
252
+ <div class="panel">
253
+ <h2>Threads</h2>
254
+ <div class="info-row"><span>Isolated:</span><span class="info-val" id="sb-status">?</span></div>
255
+ <div class="info-row"><span>Mode:</span><span class="info-val" id="thread-mode">cooperative</span></div>
256
+ <p style="margin-top:6px">Serve with COOP/COEP for SAB threads:<br><code>python3 web/serve_coop.py 8080</code></p>
257
+ </div>
258
+
259
+ <div class="panel">
260
+ <h2>Test suite</h2>
261
+ <div class="btn-row">
262
+ <button class="btn" onclick="runWasmTests()">Check build (Node)</button>
263
+ </div>
264
+ <p style="margin-top:6px">Runs the same suite as <code>ctest</code>; MicroPython REPL goes through USB-CDC with <code>-stdin</code>.</p>
265
+ </div>
266
+
267
+ <div class="panel">
268
+ <h2>What this bench covers</h2>
269
+ <p style="margin-bottom:8px">Two chips, three cores. Pick a firmware above and press Run; the serial well shows exactly what the chip would print.</p>
270
+ <table class="support" style="min-width:0;font-size:12px">
271
+ <thead><tr><th scope="col">Chip</th><th scope="col">Core</th><th scope="col">Try first</th></tr></thead>
272
+ <tbody>
273
+ <tr><td>RP2040</td><td>Cortex-M0+</td><td>hello_world, gpio_test, MicroPython</td></tr>
274
+ <tr><td>RP2350</td><td>Cortex-M33</td><td>littleOS M33, wifi_scan M33</td></tr>
275
+ <tr><td>RP2350</td><td>Hazard3 RV32</td><td>BLE advertise RV32, wifi_ping6 RV32</td></tr>
276
+ </tbody>
277
+ </table>
278
+ <p style="margin-top:8px">Full per-core feature tables live on the <a href="./docs.html">docs page</a>.</p>
279
+ <p style="font-size:11px;opacity:.75">v1.0.2 · 474 tests green · <a href="https://www.npmjs.com/package/pico-emu">npm i pico-emu</a></p>
280
+ </div>
281
+ </div>
282
+
283
+ <div class="stage">
284
+ <div class="serial-shell">
285
+ <div class="serial-bar">
286
+ <span>Serial — UART0 + USB CDC</span>
287
+ <button class="btn" id="btn-clear">Clear</button>
288
+ </div>
289
+ <div id="serial-output" role="log" aria-label="Firmware serial output" aria-live="polite"></div>
290
+ </div>
291
+ <div class="panel">
292
+ <h2>Send input</h2>
293
+ <div style="display:flex;gap:6px;flex-wrap:wrap">
294
+ <select id="port-select" aria-label="Console port" style="max-width:130px">
295
+ <option value="auto">Auto</option>
296
+ <option value="uart0">UART0</option>
297
+ <option value="uart1">UART1</option>
298
+ <option value="usb">USB CDC</option>
299
+ </select>
300
+ <input type="text" id="uart-input" placeholder="Type to send..."
301
+ style="flex:1;min-width:140px">
302
+ <button class="btn primary" id="btn-send">Send</button>
303
+ </div>
304
+ <p style="margin-top:6px" id="port-hint">Auto: USB when enumerated, else UART0.</p>
305
+ </div>
306
+ <div class="gpio-board">
307
+ <h2>Pico header — live pin map</h2>
308
+ <p class="gpio-note">Round pads like the physical 2×20 header, wired to whichever core is running. Pin 25 is the on-board LED; pins 23–25 and 29 double as the CYW43 WiFi lines on Pico W boards.</p>
309
+ <h3>Bank 0, pins 0–15</h3>
310
+ <div class="gpio-grid" id="gpio-low"></div>
311
+ </div>
312
+ <div class="gpio-board">
313
+ <h3>Bank 0, pins 16–29</h3>
314
+ <div class="gpio-grid" id="gpio-high"></div>
315
+ </div>
316
+ </div>
317
+ </div>
318
+
319
+ <footer class="page-footer">
320
+ <span>Pico-emu — a browser bench for RP2040 and RP2350 firmware.</span>
321
+ <nav aria-label="Footer">
322
+ <a href="./about.html">About this project</a>
323
+ <a href="./docs.html">Support tables</a>
324
+ <a href="https://github.com/danish9661/pico-emulator">GitHub repo</a>
325
+ <a href="https://github.com/Night-Traders-Dev/Bramble">Upstream Bramble</a>
326
+ </nav>
327
+ </footer>
328
+
329
+ <script type="module">
330
+ import PicoemuModule from './picoemu.wasm.js';
331
+
332
+ let mod = null;
333
+ let running = false;
334
+ let rafId = null;
335
+ // Measured 2026-09-13 (Node 22, x86-64, hello_rv32): ~24 MIPS flat for
336
+ // 100k-2.5M chunks; littleOS/wifi ~18-26 MIPS (peripheral-heavy paths
337
+ // cost more per step). 500k steps ~= 21-28ms wall -> fits a 60fps frame
338
+ // with headroom for UART/GPIO/DOM; 2.5M ~= 103-140ms and janks the tab.
339
+ // Full 125MHz realtime would need 125 MIPS, so guests run ~1/6 realtime.
340
+ const CYCLES_PER_FRAME = 500_000;
341
+ let lastFrameTime = 0;
342
+ let totalInstructions = 0;
343
+ let mipsWindowSteps = 0; // steps since last MIPS sample (rate, not average)
344
+ let mipsWindowStart = 0; // performance.now() at window start
345
+ let mipsShown = 0; // last displayed MIPS (1s window, throttled)
346
+ let uartText = '';
347
+
348
+ const $ = id => document.getElementById(id);
349
+ const serialEl = $('serial-output');
350
+
351
+ function appendSerial(ch) {
352
+ uartText += ch;
353
+ if (uartText.length > 10000) uartText = uartText.slice(-8000);
354
+ serialEl.textContent = uartText;
355
+ serialEl.scrollTop = serialEl.scrollHeight;
356
+ }
357
+
358
+ function setRunning_ui(on) {
359
+ running = on;
360
+ const led = $('run-led');
361
+ if (led) led.classList.toggle('live', !!on);
362
+ }
363
+
364
+ function updateGpio() {
365
+ const lo = $('gpio-low');
366
+ const hi = $('gpio-high');
367
+ if (!lo.children.length) {
368
+ for (let i = 0; i < 16; i++) lo.innerHTML += `<div class="gpio-pin" id="gpio-${i}">${i}</div>`;
369
+ for (let i = 16; i < 30; i++) hi.innerHTML += `<div class="gpio-pin" id="gpio-${i}">${i}</div>`;
370
+ }
371
+ for (let i = 0; i < 30; i++) {
372
+ const val = mod._picoemu_get_gpio_raw(i) || mod._picoemu_get_gpio(i);
373
+ const el = $(`gpio-${i}`);
374
+ if (el) el.className = 'gpio-pin ' + (val ? 'high' : 'low');
375
+ }
376
+ }
377
+
378
+ function updateCoreState() {
379
+ const pcBuf = mod._malloc(4);
380
+ const spBuf = mod._malloc(4);
381
+ mod._picoemu_get_core_state(0, pcBuf, spBuf);
382
+ const pc = mod.HEAPU32[pcBuf >> 2];
383
+ const sp = mod.HEAPU32[spBuf >> 2];
384
+ mod._free(pcBuf);
385
+ mod._free(spBuf);
386
+ $('core-pc').textContent = '0x' + pc.toString(16).padStart(8, '0');
387
+ $('core-sp').textContent = '0x' + sp.toString(16).padStart(8, '0');
388
+ let st = mod._picoemu_is_halted() ? 'halted' : 'running';
389
+ try { if (mod._picoemu_gdb_is_hit()) st = 'gdb-stop core' + mod._picoemu_gdb_hit_core(); } catch(e) {}
390
+ $('core-status').textContent = st;
391
+ }
392
+
393
+ function frame() {
394
+ if (!running) return;
395
+
396
+ const now = performance.now();
397
+ const elapsed = now - lastFrameTime;
398
+ if (elapsed > 16) {
399
+ const steps = mod._picoemu_step(CYCLES_PER_FRAME);
400
+ totalInstructions += steps;
401
+
402
+ // pump guest ETH frames -> gateway WebSocket (raw, no prefix)
403
+ try {
404
+ const gw = window.picoemuGW;
405
+ if (gw && gw.readyState === 1) {
406
+ for (let i = 0; i < 16; i++) {
407
+ const ptr = mod._malloc(2048);
408
+ const got = mod._picoemu_eth_pop_tx(ptr, 2048);
409
+ if (got <= 0) { mod._free(ptr); break; }
410
+ gw.send(mod.HEAPU8.slice(ptr, ptr + got));
411
+ mod._free(ptr);
412
+ }
413
+ }
414
+ } catch(e) {}
415
+
416
+ // pump guest BLE HCI frames -> ble-gateway WebSocket (raw H4)
417
+ try {
418
+ const bh = window.picoemuBleHci;
419
+ if (bh && bh.readyState === 1) {
420
+ for (let i = 0; i < 16; i++) {
421
+ const ptr = mod._malloc(2048);
422
+ const got = mod._picoemu_bt_hci_pop_tx(ptr, 2048);
423
+ if (got <= 0) { mod._free(ptr); break; }
424
+ bh.send(mod.HEAPU8.slice(ptr, ptr + got));
425
+ mod._free(ptr);
426
+ }
427
+ }
428
+ } catch(e) {}
429
+
430
+ // pump GDB TX -> WebSocket (non-blocking RSP)
431
+ try {
432
+ const n = mod._picoemu_gdb_tx_len();
433
+ if (n > 0 && window.picoemuGDB && window.picoemuGDB.readyState === 1) {
434
+ const ptr = mod._malloc(n);
435
+ const got = mod._picoemu_gdb_pop_tx(ptr, n);
436
+ window.picoemuGDB.send(mod.HEAPU8.slice(ptr, ptr + got));
437
+ mod._free(ptr);
438
+ }
439
+ } catch(e) {}
440
+
441
+ // pump W5500/W6300 proxy TX -> net-proxy WebSocket (CONNECT/LISTEN/CLOSE/SEND).
442
+ // Same shared net_proxy as the Net panel; NOT the OpenHW gateway — the
443
+ // offload chips speak socket-level TCP/UDP, not raw ETH (see docs note).
444
+ // W6300 pumps identically through its own queue/exports (incl. v6 framing).
445
+ try {
446
+ const ns = window.picoemuNetSocket;
447
+ if (ns && ns.readyState === 1) {
448
+ let budget = 0;
449
+ try { budget = mod._picoemu_w5500_tx_len(); } catch(e2) { budget = 0; }
450
+ if (budget > 0) {
451
+ const ptr = mod._malloc(Math.min(budget, 8192));
452
+ const got = mod._picoemu_w5500_pop_tx(ptr, Math.min(budget, 8192));
453
+ if (got > 0) ns.send(mod.HEAPU8.slice(ptr, ptr + got));
454
+ mod._free(ptr);
455
+ }
456
+ let b6300 = 0;
457
+ try { b6300 = mod._picoemu_w6300_tx_len(); } catch(e2) { b6300 = 0; }
458
+ if (b6300 > 0) {
459
+ const ptr = mod._malloc(Math.min(b6300, 8192));
460
+ const got = mod._picoemu_w6300_pop_tx(ptr, Math.min(b6300, 8192));
461
+ if (got > 0) ns.send(mod.HEAPU8.slice(ptr, ptr + got));
462
+ mod._free(ptr);
463
+ }
464
+ }
465
+ } catch(e) {}
466
+
467
+ let ch;
468
+ while ((ch = mod._picoemu_read_uart(0)) !== -1) appendSerial(String.fromCharCode(ch));
469
+
470
+ updateCoreState();
471
+ updateGpio();
472
+ try { updatePortHint(); } catch(e) {}
473
+
474
+ // Instantaneous emulator speed: steps done in THIS frame over its own
475
+ // wall time (was: total-since-boot / frame-dt, which decayed forever).
476
+ // MIPS text refreshes at ~1Hz (native -status parity); realtime % uses
477
+ // the selected guest clock (full 125MHz needs ~125 MIPS).
478
+ mipsWindowSteps += steps;
479
+ if (!mipsWindowStart) mipsWindowStart = lastFrameTime || now;
480
+ const winDt = now - mipsWindowStart;
481
+ if (winDt >= 1000) {
482
+ mipsShown = (mipsWindowSteps / winDt) * 1000 / 1e6;
483
+ mipsWindowSteps = 0;
484
+ mipsWindowStart = now;
485
+ } else if (!isFinite(mipsShown)) {
486
+ mipsShown = 0;
487
+ }
488
+ const mips = mipsShown.toFixed(1);
489
+ const mhz = parseInt(($('clock-select') || {}).value || '125', 10) || 125;
490
+ const pct = ((mipsShown / mhz) * 100).toFixed(0);
491
+ $('core-mips').textContent = `${mips} MIPS`;
492
+ $('core-speed').textContent = `${pct}% of ${mhz}MHz`;
493
+ $('perf-info').textContent = `${mips} MIPS (${pct}% of ${mhz}MHz) | ${(totalInstructions/1e6).toFixed(1)}M total`;
494
+
495
+ lastFrameTime = now;
496
+ }
497
+
498
+ rafId = requestAnimationFrame(frame);
499
+ }
500
+
501
+ $('btn-run').onclick = () => {
502
+ if (!mod) return;
503
+ setRunning_ui(true);
504
+ lastFrameTime = performance.now();
505
+ mipsWindowSteps = 0;
506
+ mipsWindowStart = lastFrameTime;
507
+ frame();
508
+ $('status').textContent = 'Running';
509
+ };
510
+
511
+ $('btn-pause').onclick = () => {
512
+ setRunning_ui(false);
513
+ if (rafId) cancelAnimationFrame(rafId);
514
+ $('status').textContent = 'Paused';
515
+ };
516
+
517
+ $('btn-reset').onclick = () => {
518
+ if (!mod) return;
519
+ setRunning_ui(false);
520
+ if (rafId) cancelAnimationFrame(rafId);
521
+ mod._picoemu_reset();
522
+ uartText = '';
523
+ serialEl.textContent = '';
524
+ totalInstructions = 0;
525
+ mipsWindowSteps = 0;
526
+ mipsWindowStart = 0;
527
+ mipsShown = 0;
528
+ $('core-mips').textContent = '0 MIPS';
529
+ $('core-speed').textContent = '-';
530
+ $('perf-info').textContent = '';
531
+ updateCoreState();
532
+ $('status').textContent = 'Reset';
533
+ };
534
+
535
+ $('clock-select').onchange = (e) => {
536
+ if (!mod) return;
537
+ mod._picoemu_set_clock(parseInt(e.target.value));
538
+ };
539
+
540
+ $('btn-clear').onclick = () => { uartText = ''; serialEl.textContent = ''; };
541
+
542
+ function sendBytes(port, text) {
543
+ // CR submits the line: USB REPLs (MicroPython) want raw CR, UART shells
544
+ // get CR->LF. Sent only when there is text to submit.
545
+ let hasPort = false;
546
+ try { hasPort = typeof mod._picoemu_write_uart_port === 'function'; } catch(e) {}
547
+ const writeUartPort = (n, code) => {
548
+ try {
549
+ if (hasPort) { mod._picoemu_write_uart_port(n, code); return; }
550
+ } catch(e) {}
551
+ if (n === 0) mod._picoemu_write_uart(code);
552
+ };
553
+ const writeUsbRaw = (code) => {
554
+ try {
555
+ if (mod._picoemu_write_usb) {
556
+ if (mod._picoemu_write_usb(code)) return;
557
+ }
558
+ } catch(e) {}
559
+ mod._picoemu_write_uart(code);
560
+ };
561
+ const pushLine = (codes) => {
562
+ if (port === 'uart1') {
563
+ for (const c of codes) writeUartPort(1, c);
564
+ if (codes.length) writeUartPort(1, 13);
565
+ } else if (port === 'uart0') {
566
+ for (const c of codes) writeUartPort(0, c);
567
+ if (codes.length) writeUartPort(0, 13);
568
+ } else if (port === 'usb') {
569
+ for (const c of codes) writeUsbRaw(c);
570
+ if (codes.length) writeUsbRaw(13);
571
+ } else {
572
+ for (const c of codes) mod._picoemu_write_uart(c);
573
+ if (codes.length) mod._picoemu_write_uart(13);
574
+ }
575
+ };
576
+ pushLine([...text].map(ch => ch.charCodeAt(0)));
577
+ }
578
+
579
+ function updatePortHint() {
580
+ const hint = $('port-hint');
581
+ if (!hint || !mod) return;
582
+ let u0 = 0, u1 = 0, usb = 0;
583
+ try { u0 = mod._picoemu_uart_active ? mod._picoemu_uart_active(0) : 1; } catch(e) {}
584
+ try { u1 = mod._picoemu_uart_active ? mod._picoemu_uart_active(1) : 0; } catch(e) {}
585
+ try { usb = mod._picoemu_usb_active ? mod._picoemu_usb_active() : 0; } catch(e) {}
586
+ const live = [];
587
+ if (u0) live.push('UART0');
588
+ if (u1) live.push('UART1');
589
+ if (usb) live.push('USB CDC');
590
+ hint.textContent = live.length
591
+ ? `Live: ${live.join(' + ')}. UART1 appears once firmware enables it.`
592
+ : 'No console active yet — run firmware first.';
593
+ }
594
+
595
+ $('btn-send').onclick = () => {
596
+ if (!mod) return;
597
+ const text = $('uart-input').value;
598
+ const port = ($('port-select') || {}).value || 'auto';
599
+ sendBytes(port, text);
600
+ $('uart-input').value = '';
601
+ };
602
+
603
+ $('uart-input').onkeydown = (e) => {
604
+ if (e.key === 'Enter') $('btn-send').click();
605
+ };
606
+
607
+ $('btn-read-mem').onclick = () => {
608
+ if (!mod) return;
609
+ const addr = parseInt($('mem-addr').value, 16);
610
+ if (isNaN(addr)) return;
611
+ const val = mod._picoemu_mem_read32(addr);
612
+ $('mem-val').textContent = '0x' + (val >>> 0).toString(16).padStart(8, '0');
613
+ };
614
+
615
+ async function loadFirmware(data, name) {
616
+ if (!mod) return;
617
+ setRunning_ui(false);
618
+ if (rafId) cancelAnimationFrame(rafId);
619
+
620
+ // Auto-detect arch from filename
621
+ const isRV = /riscv|rv32/i.test(name);
622
+ const isPico2 = /pico2/i.test(name);
623
+ const arch = isRV ? 1 : (isPico2 ? 2 : 0);
624
+ mod._picoemu_init(arch);
625
+ mod._picoemu_set_clock(parseInt($('clock-select').value));
626
+ try {
627
+ mod._picoemu_set_cores(parseInt($('cores-select').value || '2'));
628
+ mod._picoemu_set_jit($('chk-jit')?.checked ? 1 : 0);
629
+ mod._picoemu_set_debug($('chk-debug')?.checked ? 1 : 0, -1);
630
+ } catch(e) {}
631
+
632
+ uartText = '';
633
+ serialEl.textContent = '';
634
+ totalInstructions = 0;
635
+ mipsWindowSteps = 0;
636
+ mipsWindowStart = 0;
637
+ mipsShown = 0;
638
+ $('fw-name').textContent = name;
639
+
640
+ const ptr = mod._malloc(data.length);
641
+ mod.HEAPU8.set(data, ptr);
642
+
643
+ let blocks;
644
+ if (name.endsWith('.uf2')) {
645
+ blocks = mod._picoemu_load_uf2(ptr, data.length);
646
+ $('status').textContent = `Loaded UF2 (${blocks} blocks)`;
647
+ } else {
648
+ blocks = mod._picoemu_load_elf(ptr, data.length);
649
+ $('status').textContent = blocks ? 'Loaded ELF' : 'ELF load failed';
650
+ }
651
+ mod._free(ptr);
652
+ mod._picoemu_reset();
653
+
654
+ // Keep the chosen firmware name visible: drop-zone loads and failed
655
+ // preset fetches clear all arch dropdowns back to "Pick a demo…".
656
+ const presetHit = ['demo-rp2040', 'demo-m33', 'demo-rv32']
657
+ .map(id => $(id)).find(s => s && s.value === name);
658
+ for (const id of ['demo-rp2040', 'demo-m33', 'demo-rv32']) {
659
+ const s = $(id);
660
+ if (!s) continue;
661
+ if (presetHit) { if (s !== presetHit) s.selectedIndex = 0; }
662
+ else s.selectedIndex = 0;
663
+ }
664
+ // pico-eth/pico-eth2 board (WIZnet W5500-EVB-Pico/Pico2): separate SPI
665
+ // board, off by default. picoemu_init detaches, so re-apply the UI
666
+ // selection here. Pico2 wiring is identical; only the SoC differs.
667
+ // pico-w6300/pico-w6300-2 (WIZnet W6300-EVB-Pico/Pico2): QSPI-single
668
+ // on the same PL022 path (CSn=16/RSTn=22/INTn=15).
669
+ // Single-gateway default: MACRAW socket 0 joins the CYW43 vnet bus, so
670
+ // the SAME Go gateway serves WiFi + Ethernet (no second bridge).
671
+ try {
672
+ const b = ($('board-select') || {}).value || '';
673
+ if (b === 'pico-eth' || b === 'pico-eth2') {
674
+ const spi = parseInt(($('board-spi') || {}).value || '0', 10) || 0;
675
+ const live = $('chk-board-live')?.checked ? 1 : 0;
676
+ mod._picoemu_board_eth(1, live, spi);
677
+ try { mod._picoemu_w5500_gw_enable($('chk-eth-gw')?.checked ? 1 : 0); } catch(e2) {}
678
+ $('status').textContent += ' + ' + b;
679
+ } else if (b === 'pico-w6300' || b === 'pico-w6300-2') {
680
+ const spi = parseInt(($('board-spi') || {}).value || '0', 10) || 0;
681
+ const live = $('chk-board-live')?.checked ? 1 : 0;
682
+ mod._picoemu_board_eth6300(1, live, spi);
683
+ try { mod._picoemu_w6300_gw_enable($('chk-eth-gw')?.checked ? 1 : 0); } catch(e2) {}
684
+ $('status').textContent += ' + ' + b;
685
+ }
686
+ } catch(e) {}
687
+
688
+ $('status').textContent += ` - ${name}`;
689
+ updateCoreState();
690
+ try {
691
+ const sel = $('port-select');
692
+ if (sel) {
693
+ const wantUsb = /micropython|hello_usb|usb/i.test(name);
694
+ if (wantUsb) sel.value = 'usb';
695
+ else if (sel.value === 'usb' && !wantUsb) sel.value = 'auto';
696
+ }
697
+ } catch(e) {}
698
+ updatePortHint();
699
+ }
700
+ window.loadPreset = async (name) => {
701
+ $('status').textContent = `Fetching ${name}...`;
702
+ try {
703
+ const res = await fetch('./' + name);
704
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
705
+ const buf = new Uint8Array(await res.arrayBuffer());
706
+ await loadFirmware(buf, name);
707
+ } catch(e) { $('status').textContent = `Failed ${name}: ${e.message}`; }
708
+ };
709
+ // Keep the chosen firmware visible in its dropdown: the picked select holds
710
+ // its value, the other two arch selects fall back to "Pick a demo…".
711
+ window.demoPicked = (sel) => {
712
+ if (!sel || !sel.value) return;
713
+ for (const id of ['demo-rp2040', 'demo-m33', 'demo-rv32']) {
714
+ if (id !== sel.id) {
715
+ const other = $(id);
716
+ if (other) other.selectedIndex = 0;
717
+ }
718
+ }
719
+ window.loadPreset(sel.value);
720
+ };
721
+ // OpenHW-style network gateway: raw Ethernet frames <-> gVisor NAT/DHCP.
722
+ // Same protocol the ESP/STM32 simulators use: ws://host:5090/api/network-gateway[?sessionId=room]
723
+ window.connectGateway = () => {
724
+ if (!mod) return;
725
+ try { if (window.picoemuGW) window.picoemuGW.close(); } catch(e) {}
726
+ let url = ($('gw-url').value || 'ws://localhost:5090/api/network-gateway').trim();
727
+ const room = ($('gw-room').value || '').trim();
728
+ if (room) url += (url.includes('?') ? '&' : '?') + 'sessionId=' + encodeURIComponent(room);
729
+ const ws = new WebSocket(url);
730
+ ws.binaryType = 'arraybuffer';
731
+ ws.onopen = () => {
732
+ $('gw-status').textContent = 'on';
733
+ $('status').textContent = `Gateway ${url} open`;
734
+ window.picoemuGW = ws;
735
+ try { mod._picoemu_eth_set_uplink(1); } catch(e) {}
736
+ };
737
+ ws.onclose = () => {
738
+ $('gw-status').textContent = 'off';
739
+ if (window.picoemuGW === ws) window.picoemuGW = null;
740
+ try { mod._picoemu_eth_set_uplink(0); } catch(e) {}
741
+ };
742
+ ws.onerror = () => { $('gw-status').textContent = 'err'; };
743
+ ws.onmessage = (e) => {
744
+ const arr = e.data instanceof ArrayBuffer ? new Uint8Array(e.data) : new Uint8Array(0);
745
+ if (arr.length < 14 || arr.length > 1522) return; // raw ETH frame only
746
+ const ptr = mod._malloc(arr.length);
747
+ mod.HEAPU8.set(arr, ptr);
748
+ try { mod._picoemu_eth_push_rx(ptr, arr.length); } catch(err) {}
749
+ mod._free(ptr);
750
+ };
751
+ };
752
+ // BLE HCI uplink: raw H4 packets <-> Bumble/physical via gateway ble-gateway.
753
+ // Same protocol as cli.js --ble-hci: ws://host:5090/api/ble-gateway
754
+ window.connectBleHci = () => {
755
+ if (!mod) return;
756
+ try { if (window.picoemuBleHci) window.picoemuBleHci.close(); } catch(e) {}
757
+ const url = ($('ble-url').value || 'ws://localhost:5090/api/ble-gateway').trim();
758
+ const ws = new WebSocket(url);
759
+ ws.binaryType = 'arraybuffer';
760
+ ws.onopen = () => {
761
+ $('ble-status').textContent = 'on';
762
+ $('status').textContent = `BLE HCI ${url} open`;
763
+ window.picoemuBleHci = ws;
764
+ try { mod._picoemu_bt_hci_enable(1); } catch(e) {}
765
+ };
766
+ ws.onclose = () => {
767
+ $('ble-status').textContent = 'off';
768
+ if (window.picoemuBleHci === ws) window.picoemuBleHci = null;
769
+ try { mod._picoemu_bt_hci_enable(0); } catch(e) {}
770
+ };
771
+ ws.onerror = () => { $('ble-status').textContent = 'err'; };
772
+ ws.onmessage = (e) => {
773
+ const arr = e.data instanceof ArrayBuffer ? new Uint8Array(e.data) : new Uint8Array(0);
774
+ if (arr.length < 2 || arr.length > 1088) return; // raw H4 packet only
775
+ const ptr = mod._malloc(arr.length);
776
+ mod.HEAPU8.set(arr, ptr);
777
+ try { mod._picoemu_bt_hci_push_rx(ptr, arr.length); } catch(err) {}
778
+ mod._free(ptr);
779
+ };
780
+ };
781
+ window.connectGDB = () => { // Proxy: python3 web/net_proxy.py --ws 8765 --gdb-tcp 3333
782
+ // Browser WS /gdb <-> proxy TCP :3333 <-> arm-none-eabi-gdb
783
+ const base = ($('net-url').value || 'ws://localhost:8765').replace(/\/uart\/?$/, '');
784
+ const url = base.replace(/\/$/, '') + '/gdb';
785
+ try {
786
+ const ws = new WebSocket(url);
787
+ ws.binaryType = 'arraybuffer';
788
+ ws.onopen = () => {
789
+ $('status').textContent = `GDB ${url} open -> :3333`;
790
+ window.picoemuGDB = ws;
791
+ try { mod._picoemu_gdb_enable(1); } catch(e) {}
792
+ };
793
+ const pushGdb = (u8) => {
794
+ const ptr = mod._malloc(u8.length);
795
+ mod.HEAPU8.set(u8, ptr);
796
+ try { mod._picoemu_gdb_push_rx(ptr, u8.length); } catch(e) {}
797
+ mod._free(ptr);
798
+ };
799
+ ws.onmessage = (e) => {
800
+ const u8 = e.data instanceof ArrayBuffer ? new Uint8Array(e.data)
801
+ : typeof e.data === 'string' ? new TextEncoder().encode(e.data) : new Uint8Array(e.data);
802
+ pushGdb(u8);
803
+ };
804
+ // pump TX queue -> WS each frame
805
+ if (!window._gdbPump) {
806
+ window._gdbPump = setInterval(() => {
807
+ try {
808
+ const n = mod._picoemu_gdb_tx_len();
809
+ if (n > 0 && window.picoemuGDB && window.picoemuGDB.readyState === 1) {
810
+ const ptr = mod._malloc(n);
811
+ const got = mod._picoemu_gdb_pop_tx(ptr, n);
812
+ const out = mod.HEAPU8.slice(ptr, ptr + got);
813
+ mod._free(ptr);
814
+ window.picoemuGDB.send(out);
815
+ }
816
+ if (mod._picoemu_gdb_is_hit()) {
817
+ $('core-status').textContent = 'gdb-stop core' + mod._picoemu_gdb_hit_core();
818
+ }
819
+ } catch(e) {}
820
+ }, 16);
821
+ }
822
+ ws.onclose = () => { try { mod._picoemu_gdb_enable(0); } catch(e) {} };
823
+ ws.onerror = () => $('status').textContent = `GDB ${url} error (run net_proxy.py)`;
824
+ } catch(e){ $('status').textContent = `GDB err ${e.message}`; }
825
+ };
826
+ window.connectNet = () => {
827
+ const url = $('net-url').value || 'ws://localhost:8765/uart';
828
+ const isEth = /\/eth/.test(url);
829
+ const wsUrl = isEth ? url : url;
830
+ try {
831
+ const ws = new WebSocket(wsUrl);
832
+ ws.binaryType = 'arraybuffer';
833
+ ws.onopen = () => {
834
+ $('status').textContent = `Net ${wsUrl} open`;
835
+ window.picoemuNetSocket = ws;
836
+ try {
837
+ mod._picoemu_net_set_connected(1); mod._picoemu_net_enable(1);
838
+ // If a pico-eth board is selected, the proxy speaks for it
839
+ // (same 8-socket model; picoemu_w5500_push_rx targets the board).
840
+ // pico-w6300 behaves the same via picoemu_w6300_push_rx.
841
+ // Single-gateway default keeps MACRAW on the shared vnet bus too.
842
+ if ((($('board-select') || {}).value || '').startsWith('pico-eth')) {
843
+ const spi = parseInt(($('board-spi') || {}).value || '0', 10) || 0;
844
+ try { mod._picoemu_board_eth(1, 1, spi); } catch(e2) {}
845
+ try { mod._picoemu_w5500_gw_enable($('chk-eth-gw')?.checked ? 1 : 0); } catch(e3) {}
846
+ } else if ((($('board-select') || {}).value || '').startsWith('pico-w6300')) {
847
+ const spi = parseInt(($('board-spi') || {}).value || '0', 10) || 0;
848
+ try { mod._picoemu_board_eth6300(1, 1, spi); } catch(e2) {}
849
+ try { mod._picoemu_w6300_gw_enable($('chk-eth-gw')?.checked ? 1 : 0); } catch(e3) {}
850
+ }
851
+ } catch(e) {}
852
+ };
853
+ ws.onmessage = (e) => {
854
+ const arr = e.data instanceof ArrayBuffer ? new Uint8Array(e.data)
855
+ : typeof e.data === 'string' ? new TextEncoder().encode(e.data) : new Uint8Array(0);
856
+ if (!arr.length) return;
857
+ // Demux proxy prefixes: W(sock,len,payload) / S(sock,1,code) / ETH frame / raw UART
858
+ // Proxy DATA comes as [sock<8, len_lo, len_hi, payload...] (no magic).
859
+ // When a pico-w6300 board is selected the same bytes go to the W6300.
860
+ const w6300 = (($('board-select') || {}).value || '').startsWith('pico-w6300');
861
+ const pushRx = (sock, ptr, len) => {
862
+ try {
863
+ if (w6300) mod._picoemu_w6300_push_rx(sock, ptr, len);
864
+ else mod._picoemu_w5500_push_rx(sock, ptr, len);
865
+ } catch(err) {}
866
+ };
867
+ const pushStatus = (sock, code) => {
868
+ try {
869
+ if (w6300) mod._picoemu_w6300_push_status(sock, code);
870
+ else mod._picoemu_w5500_push_status(sock, code);
871
+ } catch(err) {}
872
+ };
873
+ if (arr[0] < 8 && arr.length >= 3 && arr.length === 3 + (arr[1] | (arr[2] << 8)) && (arr[1] | (arr[2] << 8)) > 0) {
874
+ const payload = arr.slice(3);
875
+ const ptr = mod._malloc(payload.length);
876
+ mod.HEAPU8.set(payload, ptr);
877
+ pushRx(arr[0], ptr, payload.length);
878
+ mod._free(ptr);
879
+ } else if (arr[0] === 0x57 && arr.length >= 4) {
880
+ const sock = arr[1], ln = arr[2] | (arr[3] << 8);
881
+ const payload = arr.slice(4, 4 + ln);
882
+ const ptr = mod._malloc(payload.length);
883
+ mod.HEAPU8.set(payload, ptr);
884
+ pushRx(sock, ptr, payload.length);
885
+ mod._free(ptr);
886
+ } else if (arr[0] === 0x53 && arr.length >= 4) {
887
+ pushStatus(arr[1], arr[3]);
888
+ } else if (arr.length >= 6 && arr[0] === 0x45 && arr[1] === 0x54 && arr[2] === 0x48) {
889
+ const ln = arr[4] | (arr[5] << 8);
890
+ const frame = arr.slice(6, 6 + ln);
891
+ const ptr = mod._malloc(frame.length);
892
+ mod.HEAPU8.set(frame, ptr);
893
+ try { mod._picoemu_eth_push_rx(ptr, frame.length); } catch(err) {}
894
+ mod._free(ptr);
895
+ } else if (arr.length >= 14) {
896
+ // Heuristic: looks like raw ETH frame -> vnet, else UART
897
+ // Try ETH first only when /eth URL, else UART
898
+ if (isEth) {
899
+ const ptr = mod._malloc(arr.length);
900
+ mod.HEAPU8.set(arr, ptr);
901
+ try { mod._picoemu_eth_push_rx(ptr, arr.length); } catch(err) {}
902
+ mod._free(ptr);
903
+ } else {
904
+ const ptr = mod._malloc(arr.length);
905
+ mod.HEAPU8.set(arr, ptr);
906
+ try { mod._picoemu_net_push_rx(ptr, arr.length); } catch(err) {
907
+ for (const b of arr) mod._picoemu_write_uart(b);
908
+ }
909
+ mod._free(ptr);
910
+ }
911
+ } else {
912
+ const ptr = mod._malloc(arr.length);
913
+ mod.HEAPU8.set(arr, ptr);
914
+ try { mod._picoemu_net_push_rx(ptr, arr.length); } catch(err) {
915
+ for (const b of arr) mod._picoemu_write_uart(b);
916
+ }
917
+ mod._free(ptr);
918
+ }
919
+ };
920
+ ws.onclose = () => { try { mod._picoemu_net_set_connected(0); } catch(e) {} };
921
+ ws.onerror = () => $('status').textContent = `Net ${wsUrl} error (run net_proxy.py)`;
922
+ } catch(e){ $('status').textContent = `Net err ${e.message}`; }
923
+ };
924
+ // Wire BroadcastChannel RX -> UART/GPIO/ETH (peer tabs)
925
+ window.picoemuWireRx = (uart, ch) => {
926
+ try { mod._picoemu_write_uart(ch); } catch(e) {}
927
+ };
928
+ window.picoemuGpioRx = (pin, value) => {
929
+ try { mod._picoemu_mem_write32(0x40014000 + pin * 8, value ? 1 : 0); } catch(e) {}
930
+ };
931
+ window.picoemuEthRx = (data) => {
932
+ try {
933
+ const u8 = data instanceof Uint8Array ? data : new Uint8Array(data);
934
+ const ptr = mod._malloc(u8.length);
935
+ mod.HEAPU8.set(u8, ptr);
936
+ mod._picoemu_eth_push_rx(ptr, u8.length);
937
+ mod._free(ptr);
938
+ } catch(e) {}
939
+ };
940
+ window.devStart = (which) => {
941
+ try {
942
+ if (which === 'coverage') mod._picoemu_coverage_start();
943
+ else if (which === 'trace') mod._picoemu_trace_start();
944
+ else if (which === 'hotspots') mod._picoemu_hotspots_start(20);
945
+ else if (which === 'profile') mod._picoemu_profile_start();
946
+ else if (which === 'callgraph') mod._picoemu_callgraph_start();
947
+ else if (which === 'vcd') mod._picoemu_gpiotrace_start();
948
+ else if (which === 'irq') mod._picoemu_irqlat_start();
949
+ else if (which === 'stack') mod._picoemu_stackcheck_start();
950
+ else if (which === 'heatmap') mod._picoemu_heatmap_start();
951
+ $('dev-status').textContent = which + ' on';
952
+ } catch(e) { $('dev-status').textContent = 'err ' + e.message; }
953
+ };
954
+ window.devDump = () => {
955
+ try {
956
+ mod._picoemu_coverage_dump(); mod._picoemu_hotspots_report();
957
+ mod._picoemu_profile_dump(); mod._picoemu_callgraph_dump();
958
+ mod._picoemu_irqlat_report(); mod._picoemu_stackcheck_report();
959
+ mod._picoemu_heatmap_dump(); mod._picoemu_gpiotrace_stop(); mod._picoemu_trace_stop();
960
+ const files = ['/coverage.bin','/trace.bin','/profile.csv','/callgraph.dot','/gpio.vcd','/heatmap.csv'];
961
+ for (const f of files) {
962
+ try {
963
+ const bytes = mod.FS.readFile(f);
964
+ const blob = new Blob([bytes], {type:'application/octet-stream'});
965
+ const a = document.createElement('a');
966
+ a.href = URL.createObjectURL(blob); a.download = f.slice(1);
967
+ a.click(); setTimeout(()=>URL.revokeObjectURL(a.href), 2000);
968
+ } catch(e) {}
969
+ }
970
+ $('dev-status').textContent = 'dumped';
971
+ } catch(e) { $('dev-status').textContent = 'err ' + e.message; }
972
+ };
973
+ window.runWasmTests = async () => {
974
+ $('status').textContent = 'See node test-wasm.js (474 tests)';
975
+ try {
976
+ const res = await fetch('./picoemu.wasm.wasm');
977
+ $('status').textContent = `WASM ${(await res.arrayBuffer()).byteLength}B ready; run: node test-wasm.js`;
978
+ } catch(e) { $('status').textContent = 'test err ' + e.message; }
979
+ };
980
+ document.getElementById('chk-jit')?.addEventListener('change', e=>{
981
+ try { mod._picoemu_set_jit(e.target.checked ? 1 : 0); } catch(err) {}
982
+ $('status').textContent = `JIT ${e.target.checked?'on':'off'}`;
983
+ });
984
+ document.getElementById('chk-debug')?.addEventListener('change', e=>{
985
+ try { mod._picoemu_set_debug(e.target.checked ? 1 : 0, -1); } catch(err) {}
986
+ $('status').textContent = `Debug ${e.target.checked?'on':'off'}`;
987
+ });
988
+ document.getElementById('cores-select')?.addEventListener('change', e=>{
989
+ try { mod._picoemu_set_cores(parseInt(e.target.value)); } catch(err) {}
990
+ $('status').textContent = `Cores ${e.target.value} (cooperative)`;
991
+ });
992
+ document.getElementById('flash-file')?.addEventListener('change', async e=>{
993
+ const f = e.target.files[0]; if(!f || !mod) return;
994
+ const buf = new Uint8Array(await f.arrayBuffer());
995
+ const ptr = mod._malloc(buf.length); mod.HEAPU8.set(buf, ptr);
996
+ try {
997
+ const name = f.name.toLowerCase();
998
+ if (name.includes('sdcard') || name.endsWith('.img')) {
999
+ // Heuristic: SD image vs flash image by size (>4MB = SD)
1000
+ if (buf.length > 4*1024*1024) {
1001
+ mod._picoemu_sdcard_load(ptr, buf.length, 1);
1002
+ $('status').textContent = `SD ${f.name} ${buf.length} bytes on SPI1`;
1003
+ } else {
1004
+ mod._picoemu_flash_write(ptr, buf.length, 0);
1005
+ mod.ccall('flash_persist_set_path', null, ['string'], ['/flash.bin']);
1006
+ try { mod._picoemu_flash_save(); } catch(err) {}
1007
+ $('status').textContent = `Flash ${f.name} ${buf.length} bytes -> MEMFS/IDBFS`;
1008
+ }
1009
+ } else {
1010
+ mod._picoemu_flash_write(ptr, buf.length, 0);
1011
+ $('status').textContent = `Flash ${f.name} ${buf.length} bytes staged`;
1012
+ }
1013
+ } catch(err) { $('status').textContent = `Flash err ${err.message}`; }
1014
+ mod._free(ptr);
1015
+ });
1016
+
1017
+ const dropZone = $('drop-zone');
1018
+ const fileInput = $('file-input');
1019
+
1020
+ dropZone.onclick = () => fileInput.click();
1021
+ dropZone.ondragover = (e) => { e.preventDefault(); dropZone.classList.add('dragover'); };
1022
+ dropZone.ondragleave = () => dropZone.classList.remove('dragover');
1023
+ dropZone.ondrop = (e) => {
1024
+ e.preventDefault();
1025
+ dropZone.classList.remove('dragover');
1026
+ const file = e.dataTransfer.files[0];
1027
+ if (file) file.arrayBuffer().then(buf => loadFirmware(new Uint8Array(buf), file.name));
1028
+ };
1029
+ fileInput.onchange = (e) => {
1030
+ const file = e.target.files[0];
1031
+ if (file) file.arrayBuffer().then(buf => loadFirmware(new Uint8Array(buf), file.name));
1032
+ };
1033
+
1034
+ $('status').textContent = 'Initializing WASM...';
1035
+
1036
+ try {
1037
+ mod = await PicoemuModule({
1038
+ print: (t) => console.log(t),
1039
+ printErr: (t) => console.log(t)
1040
+ });
1041
+ try {
1042
+ $('sb-status').textContent = (typeof crossOriginIsolated !== 'undefined' && crossOriginIsolated) ? 'yes (SAB)' : 'no (coop)';
1043
+ $('thread-mode').textContent = (typeof crossOriginIsolated !== 'undefined' && crossOriginIsolated) ? 'coop (SAB ready, rebuild -pthread for workers)' : 'cooperative';
1044
+ } catch(e) {}
1045
+ $('status').textContent = 'Ready - drop a UF2 file to begin';
1046
+ $('btn-run').disabled = false;
1047
+ $('btn-pause').disabled = false;
1048
+ $('btn-reset').disabled = false;
1049
+ } catch (err) {
1050
+ $('status').textContent = 'Error: ' + err.message;
1051
+ console.error(err);
1052
+ }
1053
+ </script>
1054
+ </body>
1055
+ </html>