v3.0.6: RP2040 에뮬레이터 개선 — USB CDC, vendor화, 부팅 감지

- USB CDC 경로 복원: #define Serial Serial1 우회 제거
- rp2040js/avr8js/codemirror/xterm/monaco-editor vendor화
- /vendor 정적 라우팅 추가 (Express 5 catch-all 우회)
- 부팅 완료 타임아웃 감지 (Serial 출력 없어도 8초 후 RAF 전환)
- 리셋 시 Simulator 재생성으로 주변 장치 상태 초기화
- 시리얼 패널의 내부 메시지를 console.log로 이동
This commit is contained in:
kim
2026-06-19 15:49:36 +09:00
parent 23831f79ba
commit 209f3a3aef
192 changed files with 68520 additions and 76 deletions
+63
View File
@@ -12,11 +12,13 @@
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0", "@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"avr8js": "^0.21.0",
"better-sqlite3": "^12.9.0", "better-sqlite3": "^12.9.0",
"commander": "^14.0.3", "commander": "^14.0.3",
"cors": "^2.8.6", "cors": "^2.8.6",
"croner": "^10.0.1", "croner": "^10.0.1",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
"esptool-js": "^0.4.1",
"express": "^5.2.1", "express": "^5.2.1",
"imapflow": "^1.3.3", "imapflow": "^1.3.3",
"mailparser": "^3.9.8", "mailparser": "^3.9.8",
@@ -26,6 +28,7 @@
"ollama": "^0.6.3", "ollama": "^0.6.3",
"playwright": "^1.59.1", "playwright": "^1.59.1",
"pptxgenjs": "^4.0.1", "pptxgenjs": "^4.0.1",
"rp2040js": "^1.3.3",
"tesseract.js": "^7.0.0", "tesseract.js": "^7.0.0",
"ws": "^8.20.0" "ws": "^8.20.0"
}, },
@@ -714,6 +717,15 @@
"node": ">=8.0.0" "node": ">=8.0.0"
} }
}, },
"node_modules/avr8js": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/avr8js/-/avr8js-0.21.0.tgz",
"integrity": "sha512-kjFzxorERow5YFFV25ElhscG57TJqpT6M0m5GDDbXiMYoBmG6K99MQcq6AU1Ly4L+uB1g1o79dMmBNb5O+toxQ==",
"engines": {
"node": ">= 20.19.0",
"npm": ">= 10.0.0"
}
},
"node_modules/base64-js": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -1222,6 +1234,44 @@
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/esptool-js": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/esptool-js/-/esptool-js-0.4.1.tgz",
"integrity": "sha512-JhPHyjBncwnZDmOWxzjCQV5e6m1qP9kXPsZy5Zn/cu4tBOfPL8McncIzNVCzmLn7Jvgi06Jg09OK2HrkyMSboA==",
"dependencies": {
"buffer": "^6.0.3",
"pako": "^2.1.0",
"tslib": "^2.4.1"
}
},
"node_modules/esptool-js/node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/esptool-js/node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
},
"node_modules/etag": { "node_modules/etag": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -2354,6 +2404,14 @@
"node": ">= 18" "node": ">= 18"
} }
}, },
"node_modules/rp2040js": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/rp2040js/-/rp2040js-1.3.3.tgz",
"integrity": "sha512-6uE6/Ht8L654aF3fB8HS6ICmywzEq1KN71XZq45RR0BUs9SUitPoy+mySoLEHPso6mu2695RCS1hKnchRlGhMA==",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -2736,6 +2794,11 @@
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/tsx": { "node_modules/tsx": {
"version": "4.21.0", "version": "4.21.0",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+3
View File
@@ -40,11 +40,13 @@
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0", "@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"avr8js": "^0.21.0",
"better-sqlite3": "^12.9.0", "better-sqlite3": "^12.9.0",
"commander": "^14.0.3", "commander": "^14.0.3",
"cors": "^2.8.6", "cors": "^2.8.6",
"croner": "^10.0.1", "croner": "^10.0.1",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
"esptool-js": "^0.4.1",
"express": "^5.2.1", "express": "^5.2.1",
"imapflow": "^1.3.3", "imapflow": "^1.3.3",
"mailparser": "^3.9.8", "mailparser": "^3.9.8",
@@ -54,6 +56,7 @@
"ollama": "^0.6.3", "ollama": "^0.6.3",
"playwright": "^1.59.1", "playwright": "^1.59.1",
"pptxgenjs": "^4.0.1", "pptxgenjs": "^4.0.1",
"rp2040js": "^1.3.3",
"tesseract.js": "^7.0.0", "tesseract.js": "^7.0.0",
"ws": "^8.20.0" "ws": "^8.20.0"
}, },
+2 -7
View File
@@ -521,13 +521,8 @@ app.post('/api/arduino/compile', async (req, res) => {
const fqbnVendor = fqbn.split(':')[0] || ''; const fqbnVendor = fqbn.split(':')[0] || '';
const fqbnArch = fqbn.split(':')[1] || ''; const fqbnArch = fqbn.split(':')[1] || '';
// RP2040 브라우저 에뮬레이터: Serial(USB CDC)을 Serial1(UART0)으로 리다이렉트 // RP2040 브라우저 에뮬레이터: USB CDC를 그대로 사용 (Serial → UART0 우회 제거)
// USB CDC 에뮬레이션은 rp2040js에서 실용적으로 지원되지 않으므로 const sketchCode = code;
// UART0을 통해 시리얼 출력을 받는다.
const isRP2040Arch = fqbnArch === 'rp2040' || fqbnArch === 'mbed_rp2040';
const sketchCode = isRP2040Arch
? `// [에뮬레이터] Serial → Serial1(UART0) 자동 리다이렉트\n#define Serial Serial1\n\n${code}`
: code;
fs.writeFileSync(path.join(sketchDir, `${sketchName}.ino`), sketchCode); fs.writeFileSync(path.join(sketchDir, `${sketchName}.ino`), sketchCode);
// AVR 계열만 .hex 생성 (esp8266은 xtensa → .bin) // AVR 계열만 .hex 생성 (esp8266은 xtensa → .bin)
const producesHex = fqbnArch === 'avr'; const producesHex = fqbnArch === 'avr';
+6
View File
@@ -9319,6 +9319,12 @@ app.get('/api/admin/context-viewer', async (req, res) => {
res.json({ sessions: withOnline }); res.json({ sessions: withOnline });
}); });
app.use('/vendor', express.static(path.join(webUiPath, 'vendor'), {
setHeaders: (res, filePath) => {
res.setHeader('Cache-Control', 'no-cache');
if (filePath.endsWith('.js')) res.setHeader('Content-Type', 'application/javascript');
}
}));
app.use(express.static(webUiPath, { setHeaders: (res) => { res.setHeader('Cache-Control', 'no-cache'); } })); app.use(express.static(webUiPath, { setHeaders: (res) => { res.setHeader('Cache-Control', 'no-cache'); } }));
// Serve code directory files for HTML preview (window.open) // Serve code directory files for HTML preview (window.open)
+235 -54
View File
@@ -310,9 +310,9 @@
/* 속도 슬라이더 */ /* 속도 슬라이더 */
#speed-range { width:70px; height:4px; accent-color:var(--accent); vertical-align:middle; cursor:pointer; } #speed-range { width:70px; height:4px; accent-color:var(--accent); vertical-align:middle; cursor:pointer; }
</style> </style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css"> <link rel="stylesheet" href="/vendor/codemirror/lib/codemirror.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script> <script src="/vendor/codemirror/lib/codemirror.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/clike/clike.min.js"></script> <script src="/vendor/codemirror/mode/clike/clike.js"></script>
</head> </head>
<body> <body>
@@ -792,7 +792,7 @@ let CPU, AVRIOPort, AVRUSART, AVRTimer, AVRTWI,
timer0Config, timer1Config, timer2Config, twiConfig; timer0Config, timer1Config, timer2Config, twiConfig;
try { try {
const mod = await import('https://esm.sh/avr8js@0.21.0'); const mod = await import('./vendor/avr8js/index.js');
let avrInstruction; let avrInstruction;
({ CPU, AVRIOPort, AVRUSART, AVRTimer, AVRTWI, ({ CPU, AVRIOPort, AVRUSART, AVRTimer, AVRTWI,
portAConfig, portBConfig, portCConfig, portDConfig, portAConfig, portBConfig, portCConfig, portDConfig,
@@ -2117,6 +2117,54 @@ let _rp2040Sim = null;
let _rp2040Running = false; let _rp2040Running = false;
let _rp2040RafId = null; let _rp2040RafId = null;
let _rp2040UartBuf = ''; let _rp2040UartBuf = '';
let _rp2040PinStates = {};
let _rp2040UartDec = null;
let _rp2040BootromU32 = null;
let _rp2040Uf2Bytes = null;
let _rp2040Fname = 'sketch.ino';
let _rp2040BootTimeoutId = null;
let _origConsoleWarn = null;
const _RP2040_WARN_FILTER = /Write to invalid SIO address|Read from invalid SIO address|2sio\.js/i;
const _PICO_LEFT_PINS = [null,null,null,null,null,null,28,null,27,26,null,22,null,21,20,19,18,null,17,16];
function _silenceRp2040jsWarnings() {
if (_origConsoleWarn) return;
_origConsoleWarn = console.warn;
console.warn = function(...args) {
const msg = args.join(' ');
if (_RP2040_WARN_FILTER.test(msg)) return;
return _origConsoleWarn.apply(this, args);
};
}
function _restoreConsoleWarn() {
if (_origConsoleWarn) { console.warn = _origConsoleWarn; _origConsoleWarn = null; }
}
const _PICO_RIGHT_PINS = [0,1,null,2,3,4,5,null,6,7,8,9,null,10,11,12,13,null,14,15];
function _updateRp2040Board() {
const on25 = !!_rp2040PinStates[25];
const toolbar = document.getElementById('rp2040-led');
if (toolbar) toolbar.style.background = on25 ? '#4ade80' : '#1a2e1a';
const svgLed = document.getElementById('pico-led');
if (svgLed) svgLed.setAttribute('fill', on25 ? '#22c55e' : '#0a1f0a');
_PICO_LEFT_PINS.forEach((g, i) => {
if (g === null) return;
const el = document.getElementById('rp-lp-' + i);
if (!el) return;
const st = _rp2040PinStates[g];
el.setAttribute('fill', st === undefined ? '#555' : st ? '#4ade80' : '#2a3a2a');
el.setAttribute('stroke', st ? '#86efac' : '#aaa');
});
_PICO_RIGHT_PINS.forEach((g, i) => {
if (g === null) return;
const el = document.getElementById('rp-rp-' + i);
if (!el) return;
const st = _rp2040PinStates[g];
el.setAttribute('fill', st === undefined ? '#555' : st ? '#4ade80' : '#2a3a2a');
el.setAttribute('stroke', st ? '#86efac' : '#aaa');
});
}
let _avrBoardType = 'board_uno'; let _avrBoardType = 'board_uno';
let _esp8266SessionId = null; let _esp8266SessionId = null;
let _esp8266EvtSource = null; let _esp8266EvtSource = null;
@@ -3348,9 +3396,9 @@ window.stopEmulator = () => {
}; };
// ── RP2040 emulation ───────────────────────────────────────────────────────── // ── RP2040 emulation ─────────────────────────────────────────────────────────
const _PICO_BOARD_SVG = `<svg id="board-svg" viewBox="0 0 90 200" xmlns="http://www.w3.org/2000/svg"> const _PICO_BOARD_SVG = `<svg id="board-svg" viewBox="0 0 90 215" xmlns="http://www.w3.org/2000/svg">
<!-- PCB --> <!-- PCB -->
<rect x="1" y="1" width="88" height="198" rx="5" fill="#1a5c2a" stroke="#3a9a4a" stroke-width="1.2"/> <rect x="1" y="1" width="88" height="213" rx="5" fill="#1a5c2a" stroke="#3a9a4a" stroke-width="1.2"/>
<!-- USB Micro-B --> <!-- USB Micro-B -->
<rect x="31" y="0" width="28" height="10" rx="2" fill="#aaa" stroke="#ccc" stroke-width=".5"/> <rect x="31" y="0" width="28" height="10" rx="2" fill="#aaa" stroke="#ccc" stroke-width=".5"/>
<rect x="34" y="1.5" width="22" height="6" rx="1" fill="#666"/> <rect x="34" y="1.5" width="22" height="6" rx="1" fill="#666"/>
@@ -3366,33 +3414,61 @@ const _PICO_BOARD_SVG = `<svg id="board-svg" viewBox="0 0 90 200" xmlns="http://
<!-- Built-in LED GPIO25 --> <!-- Built-in LED GPIO25 -->
<circle id="pico-led" cx="76" cy="25" r="3.5" fill="#0a1f0a" stroke="#2a6a2a" stroke-width=".8"/> <circle id="pico-led" cx="76" cy="25" r="3.5" fill="#0a1f0a" stroke="#2a6a2a" stroke-width=".8"/>
<text x="76" y="20" text-anchor="middle" fill="#6aaa6a" font-size="3" font-family="monospace">LED</text> <text x="76" y="20" text-anchor="middle" fill="#6aaa6a" font-size="3" font-family="monospace">LED</text>
<!-- Power LED indicator -->
<circle id="board-pwr-led" cx="14" cy="25" r="2.5" fill="#0d2a0d" stroke="#1a5a1a" stroke-width=".6"/>
<text x="14" y="20" text-anchor="middle" fill="#6aaa6a" font-size="3" font-family="monospace">PWR</text>
<!-- BOOTSEL button --> <!-- BOOTSEL button -->
<rect x="58" y="148" width="14" height="7" rx="1.5" fill="#2a2a2a" stroke="#666" stroke-width=".5"/> <rect x="50" y="148" width="14" height="7" rx="1.5" fill="#2a2a2a" stroke="#666" stroke-width=".5"/>
<text x="65" y="153.5" text-anchor="middle" fill="#aaa" font-size="3" font-family="monospace">BOOT</text> <text x="57" y="153.5" text-anchor="middle" fill="#aaa" font-size="3" font-family="monospace">BOOT</text>
<!-- Power button -->
<g id="power-switch" onclick="togglePower()" style="cursor:pointer">
<circle id="pw-bg" cx="20" cy="197" r="7.5" fill="#1a0808" stroke="#803030" stroke-width="1.2"/>
<circle cx="20" cy="197" r="5.5" fill="none" id="pw-ring" stroke="#602020" stroke-width=".8"/>
<path id="pw-arc" d="M 16.8 194.5 A 4 4 0 1 0 23.2 194.5" stroke="#c0404088" stroke-width="1.6" fill="none" stroke-linecap="round"/>
<line id="pw-line" x1="20" y1="192.5" x2="20" y2="196.5" stroke="#c0404088" stroke-width="1.6" stroke-linecap="round"/>
<circle cx="20" cy="197" r="7.5" fill="transparent" stroke="transparent"/>
</g>
<!-- Reset button -->
<g id="reset-btn" onclick="resetEmulator()" style="cursor:pointer">
<rect x="57" y="190" width="20" height="11" rx="2.5" fill="#1a2060" stroke="#3050b0" stroke-width=".8"/>
<text x="67" y="197.5" text-anchor="middle" fill="#88aaff" font-size="3.5" font-family="monospace">RESET</text>
<rect x="57" y="190" width="20" height="11" rx="2.5" fill="transparent" stroke="transparent"/>
</g>
<!-- Left pins (20) --> <!-- Left pins (20) -->
${Array.from({length:20},(_,i)=>{ ${Array.from({length:20},(_,i)=>{
const y=18+i*9; const y=18+i*9;
const labels=['VBUS','VSYS','GND','3V3','3V3_EN','ADC_REF','GP28','AGND','GP27','GP26','RUN','GP22','GND','GP21','GP20','GP19','GP18','GND','GP17','GP16']; const labels=['VBUS','VSYS','GND','3V3','3V3_EN','ADC_REF','GP28','AGND','GP27','GP26','RUN','GP22','GND','GP21','GP20','GP19','GP18','GND','GP17','GP16'];
const gpNums=[null,null,null,null,null,null,28,null,27,26,null,22,null,21,20,19,18,null,17,16];
const isGnd=labels[i].includes('GND')||labels[i]==='AGND'; const isGnd=labels[i].includes('GND')||labels[i]==='AGND';
const is3v=labels[i].includes('3V3')||labels[i]==='VBUS'||labels[i]==='VSYS'; const is3v=labels[i].includes('3V3')||labels[i]==='VBUS'||labels[i]==='VSYS';
const col=isGnd?'#888':is3v?'#f80':'#5cf'; const col=isGnd?'#888':is3v?'#f80':'#5cf';
return `<rect x="1" y="${y}" width="6" height="5" rx="1" fill="#888" stroke="#aaa" stroke-width=".4"/> const gp=gpNums[i];
const id=gp!==null?` id="rp-lp-${i}"`:'';
return `<rect${id} x="1" y="${y}" width="6" height="5" rx="1" fill="#555" stroke="#aaa" stroke-width=".4"/>
<text x="9" y="${y+4}" fill="${col}" font-size="3" font-family="monospace">${labels[i]}</text>`; <text x="9" y="${y+4}" fill="${col}" font-size="3" font-family="monospace">${labels[i]}</text>`;
}).join('')} }).join('')}
<!-- Right pins (20) --> <!-- Right pins (20) -->
${Array.from({length:20},(_,i)=>{ ${Array.from({length:20},(_,i)=>{
const y=18+i*9; const y=18+i*9;
const labels=['GP0','GP1','GND','GP2','GP3','GP4','GP5','GND','GP6','GP7','GP8','GP9','GND','GP10','GP11','GP12','GP13','GND','GP14','GP15']; const labels=['GP0','GP1','GND','GP2','GP3','GP4','GP5','GND','GP6','GP7','GP8','GP9','GND','GP10','GP11','GP12','GP13','GND','GP14','GP15'];
const gpNums=[0,1,null,2,3,4,5,null,6,7,8,9,null,10,11,12,13,null,14,15];
const isGnd=labels[i].includes('GND'); const isGnd=labels[i].includes('GND');
const col=isGnd?'#888':'#5cf'; const col=isGnd?'#888':'#5cf';
return `<rect x="83" y="${y}" width="6" height="5" rx="1" fill="#888" stroke="#aaa" stroke-width=".4"/> const gp=gpNums[i];
const id=gp!==null?` id="rp-rp-${i}"`:'';
return `<rect${id} x="83" y="${y}" width="6" height="5" rx="1" fill="#555" stroke="#aaa" stroke-width=".4"/>
<text x="81" y="${y+4}" text-anchor="end" fill="${col}" font-size="3" font-family="monospace">${labels[i]}</text>`; <text x="81" y="${y+4}" text-anchor="end" fill="${col}" font-size="3" font-family="monospace">${labels[i]}</text>`;
}).join('')} }).join('')}
</svg>`; </svg>`;
function _rp2040Start() { function _rp2040Start() {
if (_rp2040Running || !_rp2040Sim) return; if (!_rp2040Sim) return;
// 이미 실행 중이면 무시 (정지/시작 토글용)
if (_rp2040Running) return;
// 이전 스케줄러가 남아 있을 수 있으므로 먼저 정리
_rp2040Stop();
_rp2040Running = true; _rp2040Running = true;
_updatePowerSwitch();
const NS = 8; // 125 MHz → 8 ns/cycle const NS = 8; // 125 MHz → 8 ns/cycle
const rp2040 = _rp2040Sim.rp2040; const rp2040 = _rp2040Sim.rp2040;
const clock = _rp2040Sim.clock; const clock = _rp2040Sim.clock;
@@ -3510,6 +3586,29 @@ function _rp2040Stop() {
clearTimeout(_rp2040RafId); clearTimeout(_rp2040RafId);
_rp2040RafId = null; _rp2040RafId = null;
} }
if (_rp2040BootTimeoutId) {
clearTimeout(_rp2040BootTimeoutId);
_rp2040BootTimeoutId = null;
}
}
function _rp2040MarkBooted() {
if (_rp2040Sim && !_rp2040Sim._booted) {
_rp2040Sim._booted = true;
if (_rp2040BootTimeoutId) { clearTimeout(_rp2040BootTimeoutId); _rp2040BootTimeoutId = null; }
console.log('[RP2040] 부팅 완료');
}
}
function _rp2040ScheduleBootTimeout(ms = 8000) {
if (_rp2040BootTimeoutId) clearTimeout(_rp2040BootTimeoutId);
_rp2040BootTimeoutId = setTimeout(() => {
_rp2040BootTimeoutId = null;
if (_rp2040Sim && !_rp2040Sim._booted) {
_rp2040Sim._booted = true;
console.log('[RP2040] 부팅 완료 (타임아웃)');
}
}, ms);
} }
function _loadUF2(data, rp2040) { function _loadUF2(data, rp2040) {
@@ -3534,12 +3633,18 @@ async function initRP2040Mode(uf2B64, fname) {
if (footerEl) footerEl.textContent = 'rp2040js · RP2040 @ 125 MHz'; if (footerEl) footerEl.textContent = 'rp2040js · RP2040 @ 125 MHz';
document.getElementById('btn-compile').style.display = 'none'; document.getElementById('btn-compile').style.display = 'none';
// rp2040js의 SIO 미구현 주소 경고 콘솔 범람 방지
_silenceRp2040jsWarnings();
let Simulator, USBCDC; let Simulator, USBCDC;
try { try {
({ Simulator, USBCDC } = await import('https://esm.sh/rp2040js@1.3.3')); ({ Simulator, USBCDC } = await import('./vendor/rp2040js/index.js'));
window._rp2040Modules = { Simulator, USBCDC };
} catch(e) { showOverlayError('rp2040js 로드 실패\n' + e.message); return; } } catch(e) { showOverlayError('rp2040js 로드 실패\n' + e.message); return; }
const uf2Bytes = Uint8Array.from(atob(uf2B64), c => c.charCodeAt(0)); const uf2Bytes = Uint8Array.from(atob(uf2B64), c => c.charCodeAt(0));
_rp2040Uf2Bytes = uf2Bytes;
_rp2040Fname = fname || 'sketch.ino';
const sim = new Simulator(); const sim = new Simulator();
_rp2040Sim = sim; _rp2040Sim = sim;
@@ -3551,8 +3656,9 @@ async function initRP2040Mode(uf2B64, fname) {
const bootrom = Uint8Array.from(atob(brData.bin), c => c.charCodeAt(0)); const bootrom = Uint8Array.from(atob(brData.bin), c => c.charCodeAt(0));
if (bootrom.length !== 16 * 1024) throw new Error('bootrom size mismatch: ' + bootrom.length); if (bootrom.length !== 16 * 1024) throw new Error('bootrom size mismatch: ' + bootrom.length);
const bootromU32 = new Uint32Array(bootrom.buffer, bootrom.byteOffset, 4096); const bootromU32 = new Uint32Array(bootrom.buffer, bootrom.byteOffset, 4096);
_rp2040BootromU32 = bootromU32;
sim.rp2040.loadBootrom(bootromU32); sim.rp2040.loadBootrom(bootromU32);
appendSerial(`[RP2040 bootrom 로드 완료: ${bootrom.length} bytes]\n`, 'sys'); console.log(`[RP2040] bootrom 로드 완료: ${bootrom.length} bytes`);
} catch(e) { } catch(e) {
appendSerial('[RP2040 bootrom 로드 실패: ' + e.message + ']\n', 'error'); appendSerial('[RP2040 bootrom 로드 실패: ' + e.message + ']\n', 'error');
// bootrom 없이도 계속 시도 (helper 호출 시 멈출 수 있음) // bootrom 없이도 계속 시도 (helper 호출 시 멈출 수 있음)
@@ -3582,16 +3688,14 @@ async function initRP2040Mode(uf2B64, fname) {
// 4) CPU 리셋 (VTOR → flash 앱 vector 테이블에서 SP/PC 로드) // 4) CPU 리셋 (VTOR → flash 앱 vector 테이블에서 SP/PC 로드)
sim.rp2040.core.reset(); sim.rp2040.core.reset();
// rp2040js 로거 경고 억제: 미구현 명령어·주변장치 warn이 콘솔에 범람하는 것을 방지 // rp2040js 로거 억제: SEV, 미구현 명령어·주변장치 로그가 콘솔에 범람하는 것을 방지
{ {
const logger = sim.rp2040.logger; const noop = () => {};
if (logger && typeof logger.warn === 'function') { for (const logger of [sim.rp2040.logger, sim.rp2040.core?.logger]) {
logger.warn = () => {}; // 모든 warn 억제 (error/info는 유지) if (!logger) continue;
} for (const lvl of ['debug', 'info', 'log', 'warn']) {
// core가 별도 logger를 가질 경우도 처리 if (typeof logger[lvl] === 'function') logger[lvl] = noop;
const clogger = sim.rp2040.core?.logger; }
if (clogger && clogger !== logger && typeof clogger.warn === 'function') {
clogger.warn = () => {};
} }
} }
@@ -3611,18 +3715,10 @@ async function initRP2040Mode(uf2B64, fname) {
}; };
} }
// Serial 출력 발생 시 부팅 완료로 전환 (setTimeout→RAF 스케줄러 전환)
function _markBooted() {
if (_rp2040Sim && !_rp2040Sim._booted) {
_rp2040Sim._booted = true;
appendSerial('[부팅 완료]\n', 'sys');
}
}
// UART0 → Serial1 (UTF-8 스트리밍 디코딩) // UART0 → Serial1 (UTF-8 스트리밍 디코딩)
const _rp2040UartDec = new TextDecoder('utf-8', { fatal: false }); _rp2040UartDec = new TextDecoder('utf-8', { fatal: false });
sim.rp2040.uart[0].onByte = (value) => { sim.rp2040.uart[0].onByte = (value) => {
_markBooted(); _rp2040MarkBooted();
const decoded = _rp2040UartDec.decode(new Uint8Array([value]), { stream: true }); const decoded = _rp2040UartDec.decode(new Uint8Array([value]), { stream: true });
if (decoded) appendSerial(decoded); if (decoded) appendSerial(decoded);
}; };
@@ -3630,48 +3726,132 @@ async function initRP2040Mode(uf2B64, fname) {
// USB CDC → Serial (default Arduino-Pico Serial) // USB CDC → Serial (default Arduino-Pico Serial)
try { try {
const cdc = new USBCDC(sim.rp2040.usbCtrl); const cdc = new USBCDC(sim.rp2040.usbCtrl);
window._rp2040Cdc = cdc;
const dec = new TextDecoder(); const dec = new TextDecoder();
cdc.onSerialData = (buf) => { _markBooted(); appendSerial(dec.decode(buf)); }; cdc.onSerialData = (buf) => {
cdc.onDeviceConnected = () => appendSerial('[USB CDC 연결됨]\n', 'sys'); _rp2040MarkBooted();
appendSerial(dec.decode(buf));
};
cdc.onDeviceConnected = () => console.log('[RP2040] USB CDC 연결됨');
console.log('[RP2040] USB CDC 초기화 완료');
} catch(e) { } catch(e) {
appendSerial('[USB CDC 초기화 실패: ' + e.message + ']\n', 'error'); appendSerial('[USB CDC 초기화 실패: ' + e.message + ']\n', 'error');
} }
// GPIO25 = 내장 LED // 모든 GPIO 핀 리스너 등록 (GP0-GP22, GP26-GP28, GP25=LED)
sim.rp2040.gpio[25].addListener((state) => { const _allPicoGpios = [...new Set([..._PICO_LEFT_PINS, ..._PICO_RIGHT_PINS, 25].filter(g => g !== null))];
const on = state === 1; for (const g of _allPicoGpios) {
const toolbar = document.getElementById('rp2040-led'); if (!sim.rp2040.gpio[g]) continue;
if (toolbar) toolbar.style.background = on ? '#4ade80' : '#1a2e1a'; sim.rp2040.gpio[g].addListener((state) => {
const svg = document.getElementById('pico-led'); _rp2040PinStates[g] = state;
if (svg) svg.setAttribute('fill', on ? '#22c55e' : '#0a1f0a'); _updateRp2040Board();
}); });
}
document.getElementById('overlay').style.display = 'none'; document.getElementById('overlay').style.display = 'none';
const ledEl2 = document.getElementById('rp2040-led'); const ledEl2 = document.getElementById('rp2040-led');
if (ledEl2) ledEl2.style.display = 'inline-block'; if (ledEl2) ledEl2.style.display = 'inline-block';
document.getElementById('btn-stop').disabled = false; document.getElementById('btn-stop').disabled = false;
setStatus('running', '실행 중');
const resetBtn = document.getElementById('btn-reset'); const resetBtn = document.getElementById('btn-reset');
if (resetBtn) { resetBtn.disabled = false; } if (resetBtn) { resetBtn.disabled = false; }
appendSerial(`[RP2040 에뮬레이터 시작]\n[Serial → USB CDC, Serial1 → UART0]\n[VTOR=${sim.rp2040.core.VTOR.toString(16)} SP=${sim.rp2040.core.SP.toString(16)} PC=${sim.rp2040.core.PC.toString(16)}]\n`, 'sys'); console.log(`[RP2040] 에뮬레이터 시작 — VTOR=${sim.rp2040.core.VTOR.toString(16)} SP=${sim.rp2040.core.SP.toString(16)} PC=${sim.rp2040.core.PC.toString(16)}`);
_rp2040Start(); _rp2040Start();
_rp2040ScheduleBootTimeout(8000);
setStatus('running', '실행 중');
} }
window.resetEmulator = () => { window.resetEmulator = () => {
if (_IS_RP2040) { if (_IS_RP2040) {
if (!_rp2040Sim) return; if (!_rp2040Sim || !_rp2040Uf2Bytes) return;
_rp2040Stop(); _rp2040Stop();
_rp2040UartBuf = ''; _rp2040UartBuf = '';
_rp2040PinStates = {};
_updateRp2040Board();
_updatePowerSwitch();
appendSerial('\n--- 리셋 ---\n', 'sys'); appendSerial('\n--- 리셋 ---\n', 'sys');
// 주의: rp2040.reset()은 flash.fill(0xff)를 실행하므로 앱 코드가 날아감. // 주변 장치(clock/timer/uart 등) 상태가 남아 있으면 멈추므로 Simulator를 새로 만듦.
// VTOR를 다시 앱 vector table로 재설정한 뒤 core.reset()만 수행. // (RP2040.reset()은 flash를 지우지만, Simulator 재생성 + UF2 재로드는 flash를 보존)
// (flash offset 0x3000 우선, 없으면 0x100) const { Simulator } = window._rp2040Modules || {};
const fv2 = new DataView(_rp2040Sim.rp2040.flash.buffer); _rp2040Sim = Simulator ? new Simulator() : null;
const readVTOR2 = (base) => ({ sp: fv2.getUint32(base, true), pc: fv2.getUint32(base + 4, true), base }); const sim = _rp2040Sim;
const cand2 = [readVTOR2(0x3000), readVTOR2(0x100)].filter(v => v.sp && v.pc && (v.pc & ~1) >= 0x10000000); if (!sim) { appendSerial('[리셋 실패: Simulator 클래스 없음]\n', 'error'); return; }
_rp2040Sim.rp2040.core.VTOR = cand2.length ? (0x10000000 + cand2[0].base) : 0x10003000; if (_rp2040BootromU32) {
_rp2040Sim.rp2040.core.reset(); try { sim.rp2040.loadBootrom(_rp2040BootromU32); } catch(_) {}
}
_loadUF2(_rp2040Uf2Bytes, sim.rp2040);
// 벡터 테이블에서 SP/PC/VTOR 복원
const fv2 = new DataView(sim.rp2040.flash.buffer);
const readEntry = (base) => ({ sp: fv2.getUint32(base, true), pc: fv2.getUint32(base + 4, true) & ~1, base });
const cands = [readEntry(0x3000), readEntry(0x100)].filter(v => v.sp > 0x20000000 && v.pc >= 0x10000000);
const entry = cands[0] || readEntry(0x3000);
sim.rp2040.core.VTOR = 0x10000000 + entry.base;
sim.rp2040.core.SP = entry.sp;
sim.rp2040.core.PC = entry.pc;
// rp2040js 로거 억제 (info/debug/warn 등 콘솔 범람 방지)
{
const noop = () => {};
for (const logger of [sim.rp2040.logger, sim.rp2040.core?.logger]) {
if (!logger) continue;
for (const lvl of ['debug', 'info', 'log', 'warn']) {
if (typeof logger[lvl] === 'function') logger[lvl] = noop;
}
}
}
// 예외 패치 재설정
{
const core = sim.rp2040.core;
const _origException = core.exceptionEntry.bind(core);
core.exceptionEntry = function(exceptionNumber) {
if (exceptionNumber === 3) {
_rp2040Running = false;
_origException(exceptionNumber);
appendSerial(`\n[HardFault (LR=${this.LR.toString(16)}, PC=${this.PC.toString(16)}) — 실행 중지]\n`, 'error');
} else {
_origException(exceptionNumber);
}
};
}
// UART / USB CDC 재연결
_rp2040UartDec = new TextDecoder('utf-8', { fatal: false });
sim.rp2040.uart[0].onByte = (value) => {
_rp2040MarkBooted();
const decoded = _rp2040UartDec.decode(new Uint8Array([value]), { stream: true });
if (decoded) appendSerial(decoded);
};
try {
let USBCDC;
// eslint-disable-next-line no-undef
({ USBCDC } = window._rp2040Modules || {});
if (USBCDC) {
const cdc = new USBCDC(sim.rp2040.usbCtrl);
window._rp2040Cdc = cdc;
const dec = new TextDecoder();
cdc.onSerialData = (buf) => {
_rp2040MarkBooted();
appendSerial(dec.decode(buf));
};
cdc.onDeviceConnected = () => console.log('[RP2040] USB CDC 연결됨');
console.log('[RP2040] USB CDC 초기화 완료');
}
} catch(_) {}
// GPIO 리스너 재등록
const _allPicoGpios = [...new Set([..._PICO_LEFT_PINS, ..._PICO_RIGHT_PINS, 25].filter(g => g !== null))];
for (const g of _allPicoGpios) {
if (!sim.rp2040.gpio[g]) continue;
sim.rp2040.gpio[g].addListener((state) => {
_rp2040PinStates[g] = state;
_updateRp2040Board();
});
}
_rp2040Start(); _rp2040Start();
_rp2040ScheduleBootTimeout(8000);
setStatus('running', '실행 중');
return; return;
} }
if (!hexData) return; if (!hexData) return;
@@ -3704,7 +3884,7 @@ function _updatePowerSwitch() {
const ring = document.getElementById('pw-ring'); const ring = document.getElementById('pw-ring');
const pwrLed = document.getElementById('board-pwr-led'); const pwrLed = document.getElementById('board-pwr-led');
if (!arc) return; if (!arc) return;
const isRunning = running; const isRunning = _IS_RP2040 ? _rp2040Running : running;
if (isRunning) { if (isRunning) {
arc.setAttribute('stroke','#22c55e'); arc.setAttribute('stroke','#22c55e');
line.setAttribute('stroke','#22c55e'); line.setAttribute('stroke','#22c55e');
@@ -3750,6 +3930,7 @@ window.togglePower = () => {
if (_IS_RP2040 && _rp2040Sim) { if (_IS_RP2040 && _rp2040Sim) {
if (_rp2040Running) { _rp2040Stop(); setStatus('stopped', '정지'); } if (_rp2040Running) { _rp2040Stop(); setStatus('stopped', '정지'); }
else { _rp2040Start(); setStatus('running', '실행 중'); } else { _rp2040Start(); setStatus('running', '실행 중'); }
_updatePowerSwitch();
return; return;
} }
if (running) { stopEmulator(); return; } if (running) { stopEmulator(); return; }
+2 -2
View File
@@ -14,8 +14,8 @@
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=JetBrains+Mono:wght@400;500;600&family=Fira+Code:wght@400;500;600&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=JetBrains+Mono:wght@400;500;600&family=Fira+Code:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png"> <link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<!-- CodeMirror + xterm (UMD libs loaded before Monaco's AMD loader to avoid anonymous define() clashes) --> <!-- CodeMirror + xterm (UMD libs loaded before Monaco's AMD loader to avoid anonymous define() clashes) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css"> <link rel="stylesheet" href="/vendor/codemirror/lib/codemirror.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script> <script src="/vendor/codemirror/lib/codemirror.js"></script>
<link rel="stylesheet" href="/vendor/xterm.css"> <link rel="stylesheet" href="/vendor/xterm.css">
<script src="/vendor/xterm.js"></script> <script src="/vendor/xterm.js"></script>
<script src="/vendor/addon-fit.js"></script> <script src="/vendor/addon-fit.js"></script>
+13 -13
View File
@@ -9,23 +9,23 @@
<link href="https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700;800&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700;800&display=swap" rel="stylesheet">
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png"> <link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
<link rel="apple-touch-icon" sizes="128x128" href="cherry_logo.png"> <link rel="apple-touch-icon" sizes="128x128" href="cherry_logo.png">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css"> <link rel="stylesheet" href="/vendor/codemirror/lib/codemirror.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script> <script src="/vendor/codemirror/lib/codemirror.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/xml/xml.min.js"></script> <script src="/vendor/codemirror/mode/xml/xml.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/css/css.min.js"></script> <script src="/vendor/codemirror/mode/css/css.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/javascript/javascript.min.js"></script> <script src="/vendor/codemirror/mode/javascript/javascript.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/htmlmixed/htmlmixed.min.js"></script> <script src="/vendor/codemirror/mode/htmlmixed/htmlmixed.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/python/python.min.js"></script> <script src="/vendor/codemirror/mode/python/python.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/markdown/markdown.min.js"></script> <script src="/vendor/codemirror/mode/markdown/markdown.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css"> <link rel="stylesheet" href="/vendor/xterm.css">
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script> <script src="/vendor/xterm.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script> <script src="/vendor/addon-fit.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.9.0/lib/xterm-addon-web-links.js"></script> <script src="/vendor/addon-web-links.js"></script>
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
<script src="https://cdn.jsdelivr.net/npm/@tonejs/midi@2.0.28/build/Midi.js"></script> <script src="https://cdn.jsdelivr.net/npm/@tonejs/midi@2.0.28/build/Midi.js"></script>
<script src="https://cdn.jsdelivr.net/combine/npm/tone@14,npm/@magenta/music@1.23.1/es6/core.js,npm/html-midi-player@1.6.0" defer></script> <script src="https://cdn.jsdelivr.net/combine/npm/tone@14,npm/@magenta/music@1.23.1/es6/core.js,npm/html-midi-player@1.6.0" defer></script>
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script> <script src="/vendor/vs/loader.js"></script>
<script> <script>
// Hide AMD from deferred UMD libs (tone, @magenta/music, html-midi-player) so they // Hide AMD from deferred UMD libs (tone, @magenta/music, html-midi-player) so they
// don't call Monaco's anonymous define() — that would cause "Can only have one // don't call Monaco's anonymous define() — that would cause "Can only have one
+2
View File
@@ -0,0 +1,2 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(self,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core,t=e._renderService.dimensions;if(0===t.css.cell.width||0===t.css.cell.height)return;const r=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(i.getPropertyValue("height")),s=Math.max(0,parseInt(i.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),l=o-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=s-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-r;return{cols:Math.max(2,Math.floor(a/t.css.cell.width)),rows:Math.max(1,Math.floor(l/t.css.cell.height))}}}})(),e})()));
//# sourceMappingURL=addon-fit.js.map
+2
View File
@@ -0,0 +1,2 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.WebLinksAddon=t():e.WebLinksAddon=t()}(self,(()=>(()=>{"use strict";var e={6:(e,t)=>{function n(e){try{const t=new URL(e),n=t.password&&t.username?`${t.protocol}//${t.username}:${t.password}@${t.host}`:t.username?`${t.protocol}//${t.username}@${t.host}`:`${t.protocol}//${t.host}`;return e.toLocaleLowerCase().startsWith(n.toLocaleLowerCase())}catch(e){return!1}}Object.defineProperty(t,"__esModule",{value:!0}),t.LinkComputer=t.WebLinkProvider=void 0,t.WebLinkProvider=class{constructor(e,t,n,o={}){this._terminal=e,this._regex=t,this._handler=n,this._options=o}provideLinks(e,t){const n=o.computeLink(e,this._regex,this._terminal,this._handler);t(this._addCallbacks(n))}_addCallbacks(e){return e.map((e=>(e.leave=this._options.leave,e.hover=(t,n)=>{if(this._options.hover){const{range:o}=e;this._options.hover(t,n,o)}},e)))}};class o{static computeLink(e,t,r,i){const s=new RegExp(t.source,(t.flags||"")+"g"),[a,c]=o._getWindowedLineStrings(e-1,r),l=a.join("");let d;const p=[];for(;d=s.exec(l);){const e=d[0];if(!n(e))continue;const[t,s]=o._mapStrIdx(r,c,0,d.index),[a,l]=o._mapStrIdx(r,t,s,e.length);if(-1===t||-1===s||-1===a||-1===l)continue;const h={start:{x:s+1,y:t+1},end:{x:l,y:a+1}};p.push({range:h,text:e,activate:i})}return p}static _getWindowedLineStrings(e,t){let n,o=e,r=e,i=0,s="";const a=[];if(n=t.buffer.active.getLine(e)){const e=n.translateToString(!0);if(n.isWrapped&&" "!==e[0]){for(i=0;(n=t.buffer.active.getLine(--o))&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),n.isWrapped&&-1===s.indexOf(" ")););a.reverse()}for(a.push(e),i=0;(n=t.buffer.active.getLine(++r))&&n.isWrapped&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),-1===s.indexOf(" ")););}return[a,o]}static _mapStrIdx(e,t,n,o){const r=e.buffer.active,i=r.getNullCell();let s=n;for(;o;){const e=r.getLine(t);if(!e)return[-1,-1];for(let n=s;n<e.length;++n){e.getCell(n,i);const s=i.getChars();if(i.getWidth()&&(o-=s.length||1,n===e.length-1&&""===s)){const e=r.getLine(t+1);e&&e.isWrapped&&(e.getCell(0,i),2===i.getWidth()&&(o+=1))}if(o<0)return[t,n]}t++,s=0}return[t,s]}}t.LinkComputer=o}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var i=t[o]={exports:{}};return e[o](i,i.exports,n),i.exports}var o={};return(()=>{var e=o;Object.defineProperty(e,"__esModule",{value:!0}),e.WebLinksAddon=void 0;const t=n(6),r=/(https?|HTTPS?):[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/;function i(e,t){const n=window.open();if(n){try{n.opener=null}catch{}n.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}e.WebLinksAddon=class{constructor(e=i,t={}){this._handler=e,this._options=t}activate(e){this._terminal=e;const n=this._options,o=n.urlRegex||r;this._linkProvider=this._terminal.registerLinkProvider(new t.WebLinkProvider(this._terminal,o,this._handler,n))}dispose(){this._linkProvider?.dispose()}}})(),o})()));
//# sourceMappingURL=addon-web-links.js.map
+211
View File
@@ -0,0 +1,211 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
import { avrInterrupt } from './interrupt.js';
const registerSpace = 0x100;
const MAX_INTERRUPTS = 128; // Enough for ATMega2560
export class CPU {
constructor(progMem, sramBytes = 8192) {
this.progMem = progMem;
this.sramBytes = sramBytes;
this.data = new Uint8Array(this.sramBytes + registerSpace);
this.data16 = new Uint16Array(this.data.buffer);
this.dataView = new DataView(this.data.buffer);
this.progBytes = new Uint8Array(this.progMem.buffer);
this.readHooks = [];
this.writeHooks = [];
this.pendingInterrupts = new Array(MAX_INTERRUPTS);
this.nextClockEvent = null;
this.clockEventPool = []; // helps avoid garbage collection
/**
* Whether the program counter (PC) can address 22 bits (the default is 16)
*/
this.pc22Bits = this.progBytes.length > 0x20000;
this.gpioPorts = new Set();
this.gpioByPort = [];
/**
* This function is called by the WDR instruction. The Watchdog peripheral attaches
* to it to listen for WDR (watchdog reset).
*/
this.onWatchdogReset = () => {
/* empty by default */
};
/**
* Program counter
*/
this.pc = 0;
/**
* Clock cycle counter
*/
this.cycles = 0;
this.nextInterrupt = -1;
this.maxInterrupt = 0;
this.reset();
}
reset() {
this.SP = this.data.length - 1;
this.pc = 0;
this.pendingInterrupts.fill(null);
this.nextInterrupt = -1;
this.nextClockEvent = null;
}
readData(addr) {
if (addr >= 32 && this.readHooks[addr]) {
return this.readHooks[addr](addr);
}
return this.data[addr];
}
writeData(addr, value, mask = 0xff) {
const hook = this.writeHooks[addr];
if (hook) {
if (hook(value, this.data[addr], addr, mask)) {
return;
}
}
this.data[addr] = value;
}
get SP() {
return this.dataView.getUint16(93, true);
}
set SP(value) {
this.dataView.setUint16(93, value, true);
}
get SREG() {
return this.data[95];
}
get interruptsEnabled() {
return this.SREG & 0x80 ? true : false;
}
setInterruptFlag(interrupt) {
const { flagRegister, flagMask, enableRegister, enableMask } = interrupt;
if (interrupt.inverseFlag) {
this.data[flagRegister] &= ~flagMask;
}
else {
this.data[flagRegister] |= flagMask;
}
if (this.data[enableRegister] & enableMask) {
this.queueInterrupt(interrupt);
}
}
updateInterruptEnable(interrupt, registerValue) {
const { enableMask, flagRegister, flagMask, inverseFlag } = interrupt;
if (registerValue & enableMask) {
const bitSet = this.data[flagRegister] & flagMask;
if (inverseFlag ? !bitSet : bitSet) {
this.queueInterrupt(interrupt);
}
}
else {
this.clearInterrupt(interrupt, false);
}
}
queueInterrupt(interrupt) {
const { address } = interrupt;
this.pendingInterrupts[address] = interrupt;
if (this.nextInterrupt === -1 || this.nextInterrupt > address) {
this.nextInterrupt = address;
}
if (address > this.maxInterrupt) {
this.maxInterrupt = address;
}
}
clearInterrupt({ address, flagRegister, flagMask }, clearFlag = true) {
if (clearFlag) {
this.data[flagRegister] &= ~flagMask;
}
const { pendingInterrupts, maxInterrupt } = this;
if (!pendingInterrupts[address]) {
return;
}
pendingInterrupts[address] = null;
if (this.nextInterrupt === address) {
this.nextInterrupt = -1;
for (let i = address + 1; i <= maxInterrupt; i++) {
if (pendingInterrupts[i]) {
this.nextInterrupt = i;
break;
}
}
}
}
clearInterruptByFlag(interrupt, registerValue) {
const { flagRegister, flagMask } = interrupt;
if (registerValue & flagMask) {
this.data[flagRegister] &= ~flagMask;
this.clearInterrupt(interrupt);
}
}
addClockEvent(callback, cycles) {
const { clockEventPool } = this;
cycles = this.cycles + Math.max(1, cycles);
const maybeEntry = clockEventPool.pop();
const entry = maybeEntry !== null && maybeEntry !== void 0 ? maybeEntry : { cycles, callback, next: null };
entry.cycles = cycles;
entry.callback = callback;
let { nextClockEvent: clockEvent } = this;
let lastItem = null;
while (clockEvent && clockEvent.cycles < cycles) {
lastItem = clockEvent;
clockEvent = clockEvent.next;
}
if (lastItem) {
lastItem.next = entry;
entry.next = clockEvent;
}
else {
this.nextClockEvent = entry;
entry.next = clockEvent;
}
return callback;
}
updateClockEvent(callback, cycles) {
if (this.clearClockEvent(callback)) {
this.addClockEvent(callback, cycles);
return true;
}
return false;
}
clearClockEvent(callback) {
let { nextClockEvent: clockEvent } = this;
if (!clockEvent) {
return false;
}
const { clockEventPool } = this;
let lastItem = null;
while (clockEvent) {
if (clockEvent.callback === callback) {
if (lastItem) {
lastItem.next = clockEvent.next;
}
else {
this.nextClockEvent = clockEvent.next;
}
if (clockEventPool.length < 10) {
clockEventPool.push(clockEvent);
}
return true;
}
lastItem = clockEvent;
clockEvent = clockEvent.next;
}
return false;
}
tick() {
const { nextClockEvent } = this;
if (nextClockEvent && nextClockEvent.cycles <= this.cycles) {
nextClockEvent.callback();
this.nextClockEvent = nextClockEvent.next;
if (this.clockEventPool.length < 10) {
this.clockEventPool.push(nextClockEvent);
}
}
const { nextInterrupt } = this;
if (this.interruptsEnabled && nextInterrupt >= 0) {
const interrupt = this.pendingInterrupts[nextInterrupt];
avrInterrupt(this, interrupt.address);
if (!interrupt.constant) {
this.clearInterrupt(interrupt);
}
}
}
}
+859
View File
@@ -0,0 +1,859 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
function isTwoWordInstruction(opcode) {
return (
/* LDS */
(opcode & 0xfe0f) === 0x9000 ||
/* STS */
(opcode & 0xfe0f) === 0x9200 ||
/* CALL */
(opcode & 0xfe0e) === 0x940e ||
/* JMP */
(opcode & 0xfe0e) === 0x940c);
}
export function avrInstruction(cpu) {
const opcode = cpu.progMem[cpu.pc];
if ((opcode & 0xfc00) === 0x1c00) {
/* ADC, 0001 11rd dddd rrrr */
const d = cpu.data[(opcode & 0x1f0) >> 4];
const r = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
const sum = d + r + (cpu.data[95] & 1);
const R = sum & 255;
cpu.data[(opcode & 0x1f0) >> 4] = R;
let sreg = cpu.data[95] & 0xc0;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= (R ^ r) & (d ^ R) & 128 ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
sreg |= sum & 256 ? 1 : 0;
sreg |= 1 & ((d & r) | (r & ~R) | (~R & d)) ? 0x20 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xfc00) === 0xc00) {
/* ADD, 0000 11rd dddd rrrr */
const d = cpu.data[(opcode & 0x1f0) >> 4];
const r = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
const R = (d + r) & 255;
cpu.data[(opcode & 0x1f0) >> 4] = R;
let sreg = cpu.data[95] & 0xc0;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= (R ^ r) & (R ^ d) & 128 ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
sreg |= (d + r) & 256 ? 1 : 0;
sreg |= 1 & ((d & r) | (r & ~R) | (~R & d)) ? 0x20 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xff00) === 0x9600) {
/* ADIW, 1001 0110 KKdd KKKK */
const addr = 2 * ((opcode & 0x30) >> 4) + 24;
const value = cpu.dataView.getUint16(addr, true);
const R = (value + ((opcode & 0xf) | ((opcode & 0xc0) >> 2))) & 0xffff;
cpu.dataView.setUint16(addr, R, true);
let sreg = cpu.data[95] & 0xe0;
sreg |= R ? 0 : 2;
sreg |= 0x8000 & R ? 4 : 0;
sreg |= ~value & R & 0x8000 ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
sreg |= ~R & value & 0x8000 ? 1 : 0;
cpu.data[95] = sreg;
cpu.cycles++;
}
else if ((opcode & 0xfc00) === 0x2000) {
/* AND, 0010 00rd dddd rrrr */
const R = cpu.data[(opcode & 0x1f0) >> 4] & cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
cpu.data[(opcode & 0x1f0) >> 4] = R;
let sreg = cpu.data[95] & 0xe1;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xf000) === 0x7000) {
/* ANDI, 0111 KKKK dddd KKKK */
const R = cpu.data[((opcode & 0xf0) >> 4) + 16] & ((opcode & 0xf) | ((opcode & 0xf00) >> 4));
cpu.data[((opcode & 0xf0) >> 4) + 16] = R;
let sreg = cpu.data[95] & 0xe1;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xfe0f) === 0x9405) {
/* ASR, 1001 010d dddd 0101 */
const value = cpu.data[(opcode & 0x1f0) >> 4];
const R = (value >>> 1) | (128 & value);
cpu.data[(opcode & 0x1f0) >> 4] = R;
let sreg = cpu.data[95] & 0xe0;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= value & 1;
sreg |= ((sreg >> 2) & 1) ^ (sreg & 1) ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xff8f) === 0x9488) {
/* BCLR, 1001 0100 1sss 1000 */
cpu.data[95] &= ~(1 << ((opcode & 0x70) >> 4));
}
else if ((opcode & 0xfe08) === 0xf800) {
/* BLD, 1111 100d dddd 0bbb */
const b = opcode & 7;
const d = (opcode & 0x1f0) >> 4;
cpu.data[d] = (~(1 << b) & cpu.data[d]) | (((cpu.data[95] >> 6) & 1) << b);
}
else if ((opcode & 0xfc00) === 0xf400) {
/* BRBC, 1111 01kk kkkk ksss */
if (!(cpu.data[95] & (1 << (opcode & 7)))) {
cpu.pc = cpu.pc + (((opcode & 0x1f8) >> 3) - (opcode & 0x200 ? 0x40 : 0));
cpu.cycles++;
}
}
else if ((opcode & 0xfc00) === 0xf000) {
/* BRBS, 1111 00kk kkkk ksss */
if (cpu.data[95] & (1 << (opcode & 7))) {
cpu.pc = cpu.pc + (((opcode & 0x1f8) >> 3) - (opcode & 0x200 ? 0x40 : 0));
cpu.cycles++;
}
}
else if ((opcode & 0xff8f) === 0x9408) {
/* BSET, 1001 0100 0sss 1000 */
cpu.data[95] |= 1 << ((opcode & 0x70) >> 4);
}
else if ((opcode & 0xfe08) === 0xfa00) {
/* BST, 1111 101d dddd 0bbb */
const d = cpu.data[(opcode & 0x1f0) >> 4];
const b = opcode & 7;
cpu.data[95] = (cpu.data[95] & 0xbf) | ((d >> b) & 1 ? 0x40 : 0);
}
else if ((opcode & 0xfe0e) === 0x940e) {
/* CALL, 1001 010k kkkk 111k kkkk kkkk kkkk kkkk */
const k = cpu.progMem[cpu.pc + 1] | ((opcode & 1) << 16) | ((opcode & 0x1f0) << 13);
const ret = cpu.pc + 2;
const sp = cpu.dataView.getUint16(93, true);
const { pc22Bits } = cpu;
cpu.data[sp] = 255 & ret;
cpu.data[sp - 1] = (ret >> 8) & 255;
if (pc22Bits) {
cpu.data[sp - 2] = (ret >> 16) & 255;
}
cpu.dataView.setUint16(93, sp - (pc22Bits ? 3 : 2), true);
cpu.pc = k - 1;
cpu.cycles += pc22Bits ? 4 : 3;
}
else if ((opcode & 0xff00) === 0x9800) {
/* CBI, 1001 1000 AAAA Abbb */
const A = opcode & 0xf8;
const b = opcode & 7;
const R = cpu.readData((A >> 3) + 32);
const mask = 1 << b;
cpu.writeData((A >> 3) + 32, R & ~mask, mask);
}
else if ((opcode & 0xfe0f) === 0x9400) {
/* COM, 1001 010d dddd 0000 */
const d = (opcode & 0x1f0) >> 4;
const R = 255 - cpu.data[d];
cpu.data[d] = R;
let sreg = (cpu.data[95] & 0xe1) | 1;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xfc00) === 0x1400) {
/* CP, 0001 01rd dddd rrrr */
const val1 = cpu.data[(opcode & 0x1f0) >> 4];
const val2 = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
const R = val1 - val2;
let sreg = cpu.data[95] & 0xc0;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= 0 !== ((val1 ^ val2) & (val1 ^ R) & 128) ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
sreg |= val2 > val1 ? 1 : 0;
sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xfc00) === 0x400) {
/* CPC, 0000 01rd dddd rrrr */
const arg1 = cpu.data[(opcode & 0x1f0) >> 4];
const arg2 = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
let sreg = cpu.data[95];
const r = arg1 - arg2 - (sreg & 1);
sreg = (sreg & 0xc0) | (!r && (sreg >> 1) & 1 ? 2 : 0) | (arg2 + (sreg & 1) > arg1 ? 1 : 0);
sreg |= 128 & r ? 4 : 0;
sreg |= (arg1 ^ arg2) & (arg1 ^ r) & 128 ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
sreg |= 1 & ((~arg1 & arg2) | (arg2 & r) | (r & ~arg1)) ? 0x20 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xf000) === 0x3000) {
/* CPI, 0011 KKKK dddd KKKK */
const arg1 = cpu.data[((opcode & 0xf0) >> 4) + 16];
const arg2 = (opcode & 0xf) | ((opcode & 0xf00) >> 4);
const r = arg1 - arg2;
let sreg = cpu.data[95] & 0xc0;
sreg |= r ? 0 : 2;
sreg |= 128 & r ? 4 : 0;
sreg |= (arg1 ^ arg2) & (arg1 ^ r) & 128 ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
sreg |= arg2 > arg1 ? 1 : 0;
sreg |= 1 & ((~arg1 & arg2) | (arg2 & r) | (r & ~arg1)) ? 0x20 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xfc00) === 0x1000) {
/* CPSE, 0001 00rd dddd rrrr */
if (cpu.data[(opcode & 0x1f0) >> 4] === cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)]) {
const nextOpcode = cpu.progMem[cpu.pc + 1];
const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
cpu.pc += skipSize;
cpu.cycles += skipSize;
}
}
else if ((opcode & 0xfe0f) === 0x940a) {
/* DEC, 1001 010d dddd 1010 */
const value = cpu.data[(opcode & 0x1f0) >> 4];
const R = value - 1;
cpu.data[(opcode & 0x1f0) >> 4] = R;
let sreg = cpu.data[95] & 0xe1;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= 128 === value ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
cpu.data[95] = sreg;
}
else if (opcode === 0x9519) {
/* EICALL, 1001 0101 0001 1001 */
const retAddr = cpu.pc + 1;
const sp = cpu.dataView.getUint16(93, true);
const eind = cpu.data[0x5c];
cpu.data[sp] = retAddr & 255;
cpu.data[sp - 1] = (retAddr >> 8) & 255;
cpu.data[sp - 2] = (retAddr >> 16) & 255;
cpu.dataView.setUint16(93, sp - 3, true);
cpu.pc = ((eind << 16) | cpu.dataView.getUint16(30, true)) - 1;
cpu.cycles += 3;
}
else if (opcode === 0x9419) {
/* EIJMP, 1001 0100 0001 1001 */
const eind = cpu.data[0x5c];
cpu.pc = ((eind << 16) | cpu.dataView.getUint16(30, true)) - 1;
cpu.cycles++;
}
else if (opcode === 0x95d8) {
/* ELPM, 1001 0101 1101 1000 */
const rampz = cpu.data[0x5b];
cpu.data[0] = cpu.progBytes[(rampz << 16) | cpu.dataView.getUint16(30, true)];
cpu.cycles += 2;
}
else if ((opcode & 0xfe0f) === 0x9006) {
/* ELPM(REG), 1001 000d dddd 0110 */
const rampz = cpu.data[0x5b];
cpu.data[(opcode & 0x1f0) >> 4] =
cpu.progBytes[(rampz << 16) | cpu.dataView.getUint16(30, true)];
cpu.cycles += 2;
}
else if ((opcode & 0xfe0f) === 0x9007) {
/* ELPM(INC), 1001 000d dddd 0111 */
const rampz = cpu.data[0x5b];
const i = cpu.dataView.getUint16(30, true);
cpu.data[(opcode & 0x1f0) >> 4] = cpu.progBytes[(rampz << 16) | i];
cpu.dataView.setUint16(30, i + 1, true);
if (i === 0xffff) {
cpu.data[0x5b] = (rampz + 1) % (cpu.progBytes.length >> 16);
}
cpu.cycles += 2;
}
else if ((opcode & 0xfc00) === 0x2400) {
/* EOR, 0010 01rd dddd rrrr */
const R = cpu.data[(opcode & 0x1f0) >> 4] ^ cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
cpu.data[(opcode & 0x1f0) >> 4] = R;
let sreg = cpu.data[95] & 0xe1;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xff88) === 0x308) {
/* FMUL, 0000 0011 0ddd 1rrr */
const v1 = cpu.data[((opcode & 0x70) >> 4) + 16];
const v2 = cpu.data[(opcode & 7) + 16];
const R = (v1 * v2) << 1;
cpu.dataView.setUint16(0, R, true);
cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | ((v1 * v2) & 0x8000 ? 1 : 0);
cpu.cycles++;
}
else if ((opcode & 0xff88) === 0x380) {
/* FMULS, 0000 0011 1ddd 0rrr */
const v1 = cpu.dataView.getInt8(((opcode & 0x70) >> 4) + 16);
const v2 = cpu.dataView.getInt8((opcode & 7) + 16);
const R = (v1 * v2) << 1;
cpu.dataView.setInt16(0, R, true);
cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | ((v1 * v2) & 0x8000 ? 1 : 0);
cpu.cycles++;
}
else if ((opcode & 0xff88) === 0x388) {
/* FMULSU, 0000 0011 1ddd 1rrr */
const v1 = cpu.dataView.getInt8(((opcode & 0x70) >> 4) + 16);
const v2 = cpu.data[(opcode & 7) + 16];
const R = (v1 * v2) << 1;
cpu.dataView.setInt16(0, R, true);
cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 2 : 0) | ((v1 * v2) & 0x8000 ? 1 : 0);
cpu.cycles++;
}
else if (opcode === 0x9509) {
/* ICALL, 1001 0101 0000 1001 */
const retAddr = cpu.pc + 1;
const sp = cpu.dataView.getUint16(93, true);
const { pc22Bits } = cpu;
cpu.data[sp] = retAddr & 255;
cpu.data[sp - 1] = (retAddr >> 8) & 255;
if (pc22Bits) {
cpu.data[sp - 2] = (retAddr >> 16) & 255;
}
cpu.dataView.setUint16(93, sp - (pc22Bits ? 3 : 2), true);
cpu.pc = cpu.dataView.getUint16(30, true) - 1;
cpu.cycles += pc22Bits ? 3 : 2;
}
else if (opcode === 0x9409) {
/* IJMP, 1001 0100 0000 1001 */
cpu.pc = cpu.dataView.getUint16(30, true) - 1;
cpu.cycles++;
}
else if ((opcode & 0xf800) === 0xb000) {
/* IN, 1011 0AAd dddd AAAA */
const i = cpu.readData(((opcode & 0xf) | ((opcode & 0x600) >> 5)) + 32);
cpu.data[(opcode & 0x1f0) >> 4] = i;
}
else if ((opcode & 0xfe0f) === 0x9403) {
/* INC, 1001 010d dddd 0011 */
const d = cpu.data[(opcode & 0x1f0) >> 4];
const r = (d + 1) & 255;
cpu.data[(opcode & 0x1f0) >> 4] = r;
let sreg = cpu.data[95] & 0xe1;
sreg |= r ? 0 : 2;
sreg |= 128 & r ? 4 : 0;
sreg |= 127 === d ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xfe0e) === 0x940c) {
/* JMP, 1001 010k kkkk 110k kkkk kkkk kkkk kkkk */
cpu.pc = (cpu.progMem[cpu.pc + 1] | ((opcode & 1) << 16) | ((opcode & 0x1f0) << 13)) - 1;
cpu.cycles += 2;
}
else if ((opcode & 0xfe0f) === 0x9206) {
/* LAC, 1001 001r rrrr 0110 */
const r = (opcode & 0x1f0) >> 4;
const clear = cpu.data[r];
const value = cpu.readData(cpu.dataView.getUint16(30, true));
cpu.writeData(cpu.dataView.getUint16(30, true), value & (255 - clear));
cpu.data[r] = value;
}
else if ((opcode & 0xfe0f) === 0x9205) {
/* LAS, 1001 001r rrrr 0101 */
const r = (opcode & 0x1f0) >> 4;
const set = cpu.data[r];
const value = cpu.readData(cpu.dataView.getUint16(30, true));
cpu.writeData(cpu.dataView.getUint16(30, true), value | set);
cpu.data[r] = value;
}
else if ((opcode & 0xfe0f) === 0x9207) {
/* LAT, 1001 001r rrrr 0111 */
const r = cpu.data[(opcode & 0x1f0) >> 4];
const R = cpu.readData(cpu.dataView.getUint16(30, true));
cpu.writeData(cpu.dataView.getUint16(30, true), r ^ R);
cpu.data[(opcode & 0x1f0) >> 4] = R;
}
else if ((opcode & 0xf000) === 0xe000) {
/* LDI, 1110 KKKK dddd KKKK */
cpu.data[((opcode & 0xf0) >> 4) + 16] = (opcode & 0xf) | ((opcode & 0xf00) >> 4);
}
else if ((opcode & 0xfe0f) === 0x9000) {
/* LDS, 1001 000d dddd 0000 kkkk kkkk kkkk kkkk */
cpu.cycles++;
const value = cpu.readData(cpu.progMem[cpu.pc + 1]);
cpu.data[(opcode & 0x1f0) >> 4] = value;
cpu.pc++;
}
else if ((opcode & 0xfe0f) === 0x900c) {
/* LDX, 1001 000d dddd 1100 */
cpu.cycles++;
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(cpu.dataView.getUint16(26, true));
}
else if ((opcode & 0xfe0f) === 0x900d) {
/* LDX(INC), 1001 000d dddd 1101 */
const x = cpu.dataView.getUint16(26, true);
cpu.cycles++;
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(x);
cpu.dataView.setUint16(26, x + 1, true);
}
else if ((opcode & 0xfe0f) === 0x900e) {
/* LDX(DEC), 1001 000d dddd 1110 */
const x = cpu.dataView.getUint16(26, true) - 1;
cpu.dataView.setUint16(26, x, true);
cpu.cycles++;
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(x);
}
else if ((opcode & 0xfe0f) === 0x8008) {
/* LDY, 1000 000d dddd 1000 */
cpu.cycles++;
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(cpu.dataView.getUint16(28, true));
}
else if ((opcode & 0xfe0f) === 0x9009) {
/* LDY(INC), 1001 000d dddd 1001 */
const y = cpu.dataView.getUint16(28, true);
cpu.cycles++;
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(y);
cpu.dataView.setUint16(28, y + 1, true);
}
else if ((opcode & 0xfe0f) === 0x900a) {
/* LDY(DEC), 1001 000d dddd 1010 */
const y = cpu.dataView.getUint16(28, true) - 1;
cpu.dataView.setUint16(28, y, true);
cpu.cycles++;
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(y);
}
else if ((opcode & 0xd208) === 0x8008 &&
(opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)) {
/* LDDY, 10q0 qq0d dddd 1qqq */
cpu.cycles++;
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(cpu.dataView.getUint16(28, true) +
((opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)));
}
else if ((opcode & 0xfe0f) === 0x8000) {
/* LDZ, 1000 000d dddd 0000 */
cpu.cycles++;
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(cpu.dataView.getUint16(30, true));
}
else if ((opcode & 0xfe0f) === 0x9001) {
/* LDZ(INC), 1001 000d dddd 0001 */
const z = cpu.dataView.getUint16(30, true);
cpu.cycles++;
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(z);
cpu.dataView.setUint16(30, z + 1, true);
}
else if ((opcode & 0xfe0f) === 0x9002) {
/* LDZ(DEC), 1001 000d dddd 0010 */
const z = cpu.dataView.getUint16(30, true) - 1;
cpu.dataView.setUint16(30, z, true);
cpu.cycles++;
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(z);
}
else if ((opcode & 0xd208) === 0x8000 &&
(opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)) {
/* LDDZ, 10q0 qq0d dddd 0qqq */
cpu.cycles++;
cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(cpu.dataView.getUint16(30, true) +
((opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)));
}
else if (opcode === 0x95c8) {
/* LPM, 1001 0101 1100 1000 */
cpu.data[0] = cpu.progBytes[cpu.dataView.getUint16(30, true)];
cpu.cycles += 2;
}
else if ((opcode & 0xfe0f) === 0x9004) {
/* LPM(REG), 1001 000d dddd 0100 */
cpu.data[(opcode & 0x1f0) >> 4] = cpu.progBytes[cpu.dataView.getUint16(30, true)];
cpu.cycles += 2;
}
else if ((opcode & 0xfe0f) === 0x9005) {
/* LPM(INC), 1001 000d dddd 0101 */
const i = cpu.dataView.getUint16(30, true);
cpu.data[(opcode & 0x1f0) >> 4] = cpu.progBytes[i];
cpu.dataView.setUint16(30, i + 1, true);
cpu.cycles += 2;
}
else if ((opcode & 0xfe0f) === 0x9406) {
/* LSR, 1001 010d dddd 0110 */
const value = cpu.data[(opcode & 0x1f0) >> 4];
const R = value >>> 1;
cpu.data[(opcode & 0x1f0) >> 4] = R;
let sreg = cpu.data[95] & 0xe0;
sreg |= R ? 0 : 2;
sreg |= value & 1;
sreg |= ((sreg >> 2) & 1) ^ (sreg & 1) ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xfc00) === 0x2c00) {
/* MOV, 0010 11rd dddd rrrr */
cpu.data[(opcode & 0x1f0) >> 4] = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
}
else if ((opcode & 0xff00) === 0x100) {
/* MOVW, 0000 0001 dddd rrrr */
const r2 = 2 * (opcode & 0xf);
const d2 = 2 * ((opcode & 0xf0) >> 4);
cpu.data[d2] = cpu.data[r2];
cpu.data[d2 + 1] = cpu.data[r2 + 1];
}
else if ((opcode & 0xfc00) === 0x9c00) {
/* MUL, 1001 11rd dddd rrrr */
const R = cpu.data[(opcode & 0x1f0) >> 4] * cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
cpu.dataView.setUint16(0, R, true);
cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | (0x8000 & R ? 1 : 0);
cpu.cycles++;
}
else if ((opcode & 0xff00) === 0x200) {
/* MULS, 0000 0010 dddd rrrr */
const R = cpu.dataView.getInt8(((opcode & 0xf0) >> 4) + 16) * cpu.dataView.getInt8((opcode & 0xf) + 16);
cpu.dataView.setInt16(0, R, true);
cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | (0x8000 & R ? 1 : 0);
cpu.cycles++;
}
else if ((opcode & 0xff88) === 0x300) {
/* MULSU, 0000 0011 0ddd 0rrr */
const R = cpu.dataView.getInt8(((opcode & 0x70) >> 4) + 16) * cpu.data[(opcode & 7) + 16];
cpu.dataView.setInt16(0, R, true);
cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | (0x8000 & R ? 1 : 0);
cpu.cycles++;
}
else if ((opcode & 0xfe0f) === 0x9401) {
/* NEG, 1001 010d dddd 0001 */
const d = (opcode & 0x1f0) >> 4;
const value = cpu.data[d];
const R = 0 - value;
cpu.data[d] = R;
let sreg = cpu.data[95] & 0xc0;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= 128 === R ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
sreg |= R ? 1 : 0;
sreg |= 1 & (R | value) ? 0x20 : 0;
cpu.data[95] = sreg;
}
else if (opcode === 0) {
/* NOP, 0000 0000 0000 0000 */
/* NOP */
}
else if ((opcode & 0xfc00) === 0x2800) {
/* OR, 0010 10rd dddd rrrr */
const R = cpu.data[(opcode & 0x1f0) >> 4] | cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
cpu.data[(opcode & 0x1f0) >> 4] = R;
let sreg = cpu.data[95] & 0xe1;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xf000) === 0x6000) {
/* SBR, 0110 KKKK dddd KKKK */
const R = cpu.data[((opcode & 0xf0) >> 4) + 16] | ((opcode & 0xf) | ((opcode & 0xf00) >> 4));
cpu.data[((opcode & 0xf0) >> 4) + 16] = R;
let sreg = cpu.data[95] & 0xe1;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xf800) === 0xb800) {
/* OUT, 1011 1AAr rrrr AAAA */
cpu.writeData(((opcode & 0xf) | ((opcode & 0x600) >> 5)) + 32, cpu.data[(opcode & 0x1f0) >> 4]);
}
else if ((opcode & 0xfe0f) === 0x900f) {
/* POP, 1001 000d dddd 1111 */
const value = cpu.dataView.getUint16(93, true) + 1;
cpu.dataView.setUint16(93, value, true);
cpu.data[(opcode & 0x1f0) >> 4] = cpu.data[value];
cpu.cycles++;
}
else if ((opcode & 0xfe0f) === 0x920f) {
/* PUSH, 1001 001d dddd 1111 */
const value = cpu.dataView.getUint16(93, true);
cpu.data[value] = cpu.data[(opcode & 0x1f0) >> 4];
cpu.dataView.setUint16(93, value - 1, true);
cpu.cycles++;
}
else if ((opcode & 0xf000) === 0xd000) {
/* RCALL, 1101 kkkk kkkk kkkk */
const k = (opcode & 0x7ff) - (opcode & 0x800 ? 0x800 : 0);
const retAddr = cpu.pc + 1;
const sp = cpu.dataView.getUint16(93, true);
const { pc22Bits } = cpu;
cpu.data[sp] = 255 & retAddr;
cpu.data[sp - 1] = (retAddr >> 8) & 255;
if (pc22Bits) {
cpu.data[sp - 2] = (retAddr >> 16) & 255;
}
cpu.dataView.setUint16(93, sp - (pc22Bits ? 3 : 2), true);
cpu.pc += k;
cpu.cycles += pc22Bits ? 3 : 2;
}
else if (opcode === 0x9508) {
/* RET, 1001 0101 0000 1000 */
const { pc22Bits } = cpu;
const i = cpu.dataView.getUint16(93, true) + (pc22Bits ? 3 : 2);
cpu.dataView.setUint16(93, i, true);
cpu.pc = (cpu.data[i - 1] << 8) + cpu.data[i] - 1;
if (pc22Bits) {
cpu.pc |= cpu.data[i - 2] << 16;
}
cpu.cycles += pc22Bits ? 4 : 3;
}
else if (opcode === 0x9518) {
/* RETI, 1001 0101 0001 1000 */
const { pc22Bits } = cpu;
const i = cpu.dataView.getUint16(93, true) + (pc22Bits ? 3 : 2);
cpu.dataView.setUint16(93, i, true);
cpu.pc = (cpu.data[i - 1] << 8) + cpu.data[i] - 1;
if (pc22Bits) {
cpu.pc |= cpu.data[i - 2] << 16;
}
cpu.cycles += pc22Bits ? 4 : 3;
cpu.data[95] |= 0x80; // Enable interrupts
}
else if ((opcode & 0xf000) === 0xc000) {
/* RJMP, 1100 kkkk kkkk kkkk */
cpu.pc = cpu.pc + ((opcode & 0x7ff) - (opcode & 0x800 ? 0x800 : 0));
cpu.cycles++;
}
else if ((opcode & 0xfe0f) === 0x9407) {
/* ROR, 1001 010d dddd 0111 */
const d = cpu.data[(opcode & 0x1f0) >> 4];
const r = (d >>> 1) | ((cpu.data[95] & 1) << 7);
cpu.data[(opcode & 0x1f0) >> 4] = r;
let sreg = cpu.data[95] & 0xe0;
sreg |= r ? 0 : 2;
sreg |= 128 & r ? 4 : 0;
sreg |= 1 & d ? 1 : 0;
sreg |= ((sreg >> 2) & 1) ^ (sreg & 1) ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xfc00) === 0x800) {
/* SBC, 0000 10rd dddd rrrr */
const val1 = cpu.data[(opcode & 0x1f0) >> 4];
const val2 = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
let sreg = cpu.data[95];
const R = val1 - val2 - (sreg & 1);
cpu.data[(opcode & 0x1f0) >> 4] = R;
sreg = (sreg & 0xc0) | (!R && (sreg >> 1) & 1 ? 2 : 0) | (val2 + (sreg & 1) > val1 ? 1 : 0);
sreg |= 128 & R ? 4 : 0;
sreg |= (val1 ^ val2) & (val1 ^ R) & 128 ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xf000) === 0x4000) {
/* SBCI, 0100 KKKK dddd KKKK */
const val1 = cpu.data[((opcode & 0xf0) >> 4) + 16];
const val2 = (opcode & 0xf) | ((opcode & 0xf00) >> 4);
let sreg = cpu.data[95];
const R = val1 - val2 - (sreg & 1);
cpu.data[((opcode & 0xf0) >> 4) + 16] = R;
sreg = (sreg & 0xc0) | (!R && (sreg >> 1) & 1 ? 2 : 0) | (val2 + (sreg & 1) > val1 ? 1 : 0);
sreg |= 128 & R ? 4 : 0;
sreg |= (val1 ^ val2) & (val1 ^ R) & 128 ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xff00) === 0x9a00) {
/* SBI, 1001 1010 AAAA Abbb */
const target = ((opcode & 0xf8) >> 3) + 32;
const mask = 1 << (opcode & 7);
cpu.writeData(target, cpu.readData(target) | mask, mask);
cpu.cycles++;
}
else if ((opcode & 0xff00) === 0x9900) {
/* SBIC, 1001 1001 AAAA Abbb */
const value = cpu.readData(((opcode & 0xf8) >> 3) + 32);
if (!(value & (1 << (opcode & 7)))) {
const nextOpcode = cpu.progMem[cpu.pc + 1];
const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
cpu.cycles += skipSize;
cpu.pc += skipSize;
}
}
else if ((opcode & 0xff00) === 0x9b00) {
/* SBIS, 1001 1011 AAAA Abbb */
const value = cpu.readData(((opcode & 0xf8) >> 3) + 32);
if (value & (1 << (opcode & 7))) {
const nextOpcode = cpu.progMem[cpu.pc + 1];
const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
cpu.cycles += skipSize;
cpu.pc += skipSize;
}
}
else if ((opcode & 0xff00) === 0x9700) {
/* SBIW, 1001 0111 KKdd KKKK */
const i = 2 * ((opcode & 0x30) >> 4) + 24;
const a = cpu.dataView.getUint16(i, true);
const l = (opcode & 0xf) | ((opcode & 0xc0) >> 2);
const R = a - l;
cpu.dataView.setUint16(i, R, true);
let sreg = cpu.data[95] & 0xc0;
sreg |= R ? 0 : 2;
sreg |= 0x8000 & R ? 4 : 0;
sreg |= a & ~R & 0x8000 ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
sreg |= l > a ? 1 : 0;
sreg |= 1 & ((~a & l) | (l & R) | (R & ~a)) ? 0x20 : 0;
cpu.data[95] = sreg;
cpu.cycles++;
}
else if ((opcode & 0xfe08) === 0xfc00) {
/* SBRC, 1111 110r rrrr 0bbb */
if (!(cpu.data[(opcode & 0x1f0) >> 4] & (1 << (opcode & 7)))) {
const nextOpcode = cpu.progMem[cpu.pc + 1];
const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
cpu.cycles += skipSize;
cpu.pc += skipSize;
}
}
else if ((opcode & 0xfe08) === 0xfe00) {
/* SBRS, 1111 111r rrrr 0bbb */
if (cpu.data[(opcode & 0x1f0) >> 4] & (1 << (opcode & 7))) {
const nextOpcode = cpu.progMem[cpu.pc + 1];
const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
cpu.cycles += skipSize;
cpu.pc += skipSize;
}
}
else if (opcode === 0x9588) {
/* SLEEP, 1001 0101 1000 1000 */
/* not implemented */
}
else if (opcode === 0x95e8) {
/* SPM, 1001 0101 1110 1000 */
/* not implemented */
}
else if (opcode === 0x95f8) {
/* SPM(INC), 1001 0101 1111 1000 */
/* not implemented */
}
else if ((opcode & 0xfe0f) === 0x9200) {
/* STS, 1001 001d dddd 0000 kkkk kkkk kkkk kkkk */
const value = cpu.data[(opcode & 0x1f0) >> 4];
const addr = cpu.progMem[cpu.pc + 1];
cpu.writeData(addr, value);
cpu.pc++;
cpu.cycles++;
}
else if ((opcode & 0xfe0f) === 0x920c) {
/* STX, 1001 001r rrrr 1100 */
cpu.writeData(cpu.dataView.getUint16(26, true), cpu.data[(opcode & 0x1f0) >> 4]);
cpu.cycles++;
}
else if ((opcode & 0xfe0f) === 0x920d) {
/* STX(INC), 1001 001r rrrr 1101 */
const x = cpu.dataView.getUint16(26, true);
cpu.writeData(x, cpu.data[(opcode & 0x1f0) >> 4]);
cpu.dataView.setUint16(26, x + 1, true);
cpu.cycles++;
}
else if ((opcode & 0xfe0f) === 0x920e) {
/* STX(DEC), 1001 001r rrrr 1110 */
const i = cpu.data[(opcode & 0x1f0) >> 4];
const x = cpu.dataView.getUint16(26, true) - 1;
cpu.dataView.setUint16(26, x, true);
cpu.writeData(x, i);
cpu.cycles++;
}
else if ((opcode & 0xfe0f) === 0x8208) {
/* STY, 1000 001r rrrr 1000 */
cpu.writeData(cpu.dataView.getUint16(28, true), cpu.data[(opcode & 0x1f0) >> 4]);
cpu.cycles++;
}
else if ((opcode & 0xfe0f) === 0x9209) {
/* STY(INC), 1001 001r rrrr 1001 */
const i = cpu.data[(opcode & 0x1f0) >> 4];
const y = cpu.dataView.getUint16(28, true);
cpu.writeData(y, i);
cpu.dataView.setUint16(28, y + 1, true);
cpu.cycles++;
}
else if ((opcode & 0xfe0f) === 0x920a) {
/* STY(DEC), 1001 001r rrrr 1010 */
const i = cpu.data[(opcode & 0x1f0) >> 4];
const y = cpu.dataView.getUint16(28, true) - 1;
cpu.dataView.setUint16(28, y, true);
cpu.writeData(y, i);
cpu.cycles++;
}
else if ((opcode & 0xd208) === 0x8208 &&
(opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)) {
/* STDY, 10q0 qq1r rrrr 1qqq */
cpu.writeData(cpu.dataView.getUint16(28, true) +
((opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)), cpu.data[(opcode & 0x1f0) >> 4]);
cpu.cycles++;
}
else if ((opcode & 0xfe0f) === 0x8200) {
/* STZ, 1000 001r rrrr 0000 */
cpu.writeData(cpu.dataView.getUint16(30, true), cpu.data[(opcode & 0x1f0) >> 4]);
cpu.cycles++;
}
else if ((opcode & 0xfe0f) === 0x9201) {
/* STZ(INC), 1001 001r rrrr 0001 */
const z = cpu.dataView.getUint16(30, true);
cpu.writeData(z, cpu.data[(opcode & 0x1f0) >> 4]);
cpu.dataView.setUint16(30, z + 1, true);
cpu.cycles++;
}
else if ((opcode & 0xfe0f) === 0x9202) {
/* STZ(DEC), 1001 001r rrrr 0010 */
const i = cpu.data[(opcode & 0x1f0) >> 4];
const z = cpu.dataView.getUint16(30, true) - 1;
cpu.dataView.setUint16(30, z, true);
cpu.writeData(z, i);
cpu.cycles++;
}
else if ((opcode & 0xd208) === 0x8200 &&
(opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)) {
/* STDZ, 10q0 qq1r rrrr 0qqq */
cpu.writeData(cpu.dataView.getUint16(30, true) +
((opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)), cpu.data[(opcode & 0x1f0) >> 4]);
cpu.cycles++;
}
else if ((opcode & 0xfc00) === 0x1800) {
/* SUB, 0001 10rd dddd rrrr */
const val1 = cpu.data[(opcode & 0x1f0) >> 4];
const val2 = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
const R = val1 - val2;
cpu.data[(opcode & 0x1f0) >> 4] = R;
let sreg = cpu.data[95] & 0xc0;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= (val1 ^ val2) & (val1 ^ R) & 128 ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
sreg |= val2 > val1 ? 1 : 0;
sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xf000) === 0x5000) {
/* SUBI, 0101 KKKK dddd KKKK */
const val1 = cpu.data[((opcode & 0xf0) >> 4) + 16];
const val2 = (opcode & 0xf) | ((opcode & 0xf00) >> 4);
const R = val1 - val2;
cpu.data[((opcode & 0xf0) >> 4) + 16] = R;
let sreg = cpu.data[95] & 0xc0;
sreg |= R ? 0 : 2;
sreg |= 128 & R ? 4 : 0;
sreg |= (val1 ^ val2) & (val1 ^ R) & 128 ? 8 : 0;
sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
sreg |= val2 > val1 ? 1 : 0;
sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
cpu.data[95] = sreg;
}
else if ((opcode & 0xfe0f) === 0x9402) {
/* SWAP, 1001 010d dddd 0010 */
const d = (opcode & 0x1f0) >> 4;
const i = cpu.data[d];
cpu.data[d] = ((15 & i) << 4) | ((240 & i) >>> 4);
}
else if (opcode === 0x95a8) {
/* WDR, 1001 0101 1010 1000 */
cpu.onWatchdogReset();
}
else if ((opcode & 0xfe0f) === 0x9204) {
/* XCH, 1001 001r rrrr 0100 */
const r = (opcode & 0x1f0) >> 4;
const val1 = cpu.data[r];
const val2 = cpu.data[cpu.dataView.getUint16(30, true)];
cpu.data[cpu.dataView.getUint16(30, true)] = val1;
cpu.data[r] = val2;
}
cpu.pc = (cpu.pc + 1) % cpu.progMem.length;
cpu.cycles++;
}
+14
View File
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
export function avrInterrupt(cpu, addr) {
const sp = cpu.dataView.getUint16(93, true);
cpu.data[sp] = cpu.pc & 0xff;
cpu.data[sp - 1] = (cpu.pc >> 8) & 0xff;
if (cpu.pc22Bits) {
cpu.data[sp - 2] = (cpu.pc >> 16) & 0xff;
}
cpu.dataView.setUint16(93, sp - (cpu.pc22Bits ? 3 : 2), true);
cpu.data[95] &= 0x7f; // clear global interrupt flag
cpu.cycles += 2;
cpu.pc = addr;
}
+16
View File
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
export { CPU } from './cpu/cpu.js';
export { avrInstruction } from './cpu/instruction.js';
export { avrInterrupt } from './cpu/interrupt.js';
export { ADCMuxInputType, ADCReference, AVRADC, adcConfig, atmega328Channels, } from './peripherals/adc.js';
export { AVRClock, clockConfig } from './peripherals/clock.js';
export { AVREEPROM, EEPROMMemoryBackend, eepromConfig } from './peripherals/eeprom.js';
export { AVRIOPort, INT0, INT1, PCINT0, PCINT1, PCINT2, PinState, portAConfig, portBConfig, portCConfig, portDConfig, portEConfig, portFConfig, portGConfig, portHConfig, portJConfig, portKConfig, portLConfig, } from './peripherals/gpio.js';
export { AVRSPI, spiConfig } from './peripherals/spi.js';
export { AVRTimer, timer0Config, timer1Config, timer2Config } from './peripherals/timer.js';
export { ATtinyTimer1, attinyTimer1Config } from './peripherals/timer-attiny.js';
export * from './peripherals/twi.js';
export { AVRUSART, usart0Config } from './peripherals/usart.js';
export { AVRUSI } from './peripherals/usi.js';
export { AVRWatchdog, watchdogConfig } from './peripherals/watchdog.js';
+212
View File
@@ -0,0 +1,212 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
export var ADCReference;
(function (ADCReference) {
ADCReference[ADCReference["AVCC"] = 0] = "AVCC";
ADCReference[ADCReference["AREF"] = 1] = "AREF";
ADCReference[ADCReference["Internal1V1"] = 2] = "Internal1V1";
ADCReference[ADCReference["Internal2V56"] = 3] = "Internal2V56";
ADCReference[ADCReference["Reserved"] = 4] = "Reserved";
})(ADCReference || (ADCReference = {}));
export var ADCMuxInputType;
(function (ADCMuxInputType) {
ADCMuxInputType[ADCMuxInputType["SingleEnded"] = 0] = "SingleEnded";
ADCMuxInputType[ADCMuxInputType["Differential"] = 1] = "Differential";
ADCMuxInputType[ADCMuxInputType["Constant"] = 2] = "Constant";
ADCMuxInputType[ADCMuxInputType["Temperature"] = 3] = "Temperature";
})(ADCMuxInputType || (ADCMuxInputType = {}));
export const atmega328Channels = {
0: { type: ADCMuxInputType.SingleEnded, channel: 0 },
1: { type: ADCMuxInputType.SingleEnded, channel: 1 },
2: { type: ADCMuxInputType.SingleEnded, channel: 2 },
3: { type: ADCMuxInputType.SingleEnded, channel: 3 },
4: { type: ADCMuxInputType.SingleEnded, channel: 4 },
5: { type: ADCMuxInputType.SingleEnded, channel: 5 },
6: { type: ADCMuxInputType.SingleEnded, channel: 6 },
7: { type: ADCMuxInputType.SingleEnded, channel: 7 },
8: { type: ADCMuxInputType.Temperature },
14: { type: ADCMuxInputType.Constant, voltage: 1.1 },
15: { type: ADCMuxInputType.Constant, voltage: 0 },
};
const fallbackMuxInput = {
type: ADCMuxInputType.Constant,
voltage: 0,
};
export const adcConfig = {
ADMUX: 0x7c,
ADCSRA: 0x7a,
ADCSRB: 0x7b,
ADCL: 0x78,
ADCH: 0x79,
DIDR0: 0x7e,
adcInterrupt: 0x2a,
numChannels: 8,
muxInputMask: 0xf,
muxChannels: atmega328Channels,
adcReferences: [
ADCReference.AREF,
ADCReference.AVCC,
ADCReference.Reserved,
ADCReference.Internal1V1,
],
};
// Register bits:
const ADPS_MASK = 0x7;
const ADIE = 0x8;
const ADIF = 0x10;
const ADSC = 0x40;
const ADEN = 0x80;
const MUX_MASK = 0x1f;
const ADLAR = 0x20;
const MUX5 = 0x8;
const REFS2 = 0x8;
const REFS_MASK = 0x3;
const REFS_SHIFT = 6;
export class AVRADC {
constructor(cpu, config) {
this.cpu = cpu;
this.config = config;
/**
* ADC Channel values, in voltage (0..5). The number of channels depends on the chip.
*
* Changing the values here will change the ADC reading, unless you override onADCRead() with a custom implementation.
*/
this.channelValues = new Array(this.config.numChannels);
/** AVCC Reference voltage */
this.avcc = 5;
/** AREF Reference voltage */
this.aref = 5;
/**
* Invoked whenever the code performs an ADC read.
*
* The default implementation reads the result from the `channelValues` array, and then calls
* `completeADCRead()` after `sampleCycles` CPU cycles.
*
* If you override the default implementation, make sure to call `completeADCRead()` after
* `sampleCycles` cycles (or else the ADC read will never complete).
*/
this.onADCRead = (input) => {
var _a;
// Default implementation
let voltage = 0;
switch (input.type) {
case ADCMuxInputType.Constant:
voltage = input.voltage;
break;
case ADCMuxInputType.SingleEnded:
voltage = (_a = this.channelValues[input.channel]) !== null && _a !== void 0 ? _a : 0;
break;
case ADCMuxInputType.Differential:
voltage =
input.gain *
((this.channelValues[input.positiveChannel] || 0) -
(this.channelValues[input.negativeChannel] || 0));
break;
case ADCMuxInputType.Temperature:
voltage = 0.378125; // 25 celcius
break;
}
const rawValue = (voltage / this.referenceVoltage) * 1024;
const result = Math.min(Math.max(Math.floor(rawValue), 0), 1023);
this.cpu.addClockEvent(() => this.completeADCRead(result), this.sampleCycles);
};
this.converting = false;
this.conversionCycles = 25;
// Interrupts
this.ADC = {
address: this.config.adcInterrupt,
flagRegister: this.config.ADCSRA,
flagMask: ADIF,
enableRegister: this.config.ADCSRA,
enableMask: ADIE,
};
cpu.writeHooks[config.ADCSRA] = (value, oldValue) => {
var _a;
if (value & ADEN && !(oldValue && ADEN)) {
this.conversionCycles = 25;
}
cpu.data[config.ADCSRA] = value;
cpu.updateInterruptEnable(this.ADC, value);
if (!this.converting && value & ADSC) {
if (!(value & ADEN)) {
// Special case: reading while the ADC is not enabled should return 0
this.cpu.addClockEvent(() => this.completeADCRead(0), this.sampleCycles);
return true;
}
let channel = this.cpu.data[this.config.ADMUX] & MUX_MASK;
if (cpu.data[config.ADCSRB] & MUX5) {
channel |= 0x20;
}
channel &= config.muxInputMask;
const muxInput = (_a = config.muxChannels[channel]) !== null && _a !== void 0 ? _a : fallbackMuxInput;
this.converting = true;
this.onADCRead(muxInput);
return true; // don't update
}
};
}
completeADCRead(value) {
const { ADCL, ADCH, ADMUX, ADCSRA } = this.config;
this.converting = false;
this.conversionCycles = 13;
if (this.cpu.data[ADMUX] & ADLAR) {
this.cpu.data[ADCL] = (value << 6) & 0xff;
this.cpu.data[ADCH] = value >> 2;
}
else {
this.cpu.data[ADCL] = value & 0xff;
this.cpu.data[ADCH] = (value >> 8) & 0x3;
}
this.cpu.data[ADCSRA] &= ~ADSC;
this.cpu.setInterruptFlag(this.ADC);
}
get prescaler() {
const { ADCSRA } = this.config;
const adcsra = this.cpu.data[ADCSRA];
const adps = adcsra & ADPS_MASK;
switch (adps) {
case 0:
case 1:
return 2;
case 2:
return 4;
case 3:
return 8;
case 4:
return 16;
case 5:
return 32;
case 6:
return 64;
case 7:
default:
return 128;
}
}
get referenceVoltageType() {
var _a;
const { ADMUX, adcReferences } = this.config;
let refs = (this.cpu.data[ADMUX] >> REFS_SHIFT) & REFS_MASK;
if (adcReferences.length > 4 && this.cpu.data[ADMUX] & REFS2) {
refs |= 0x4;
}
return (_a = adcReferences[refs]) !== null && _a !== void 0 ? _a : ADCReference.Reserved;
}
get referenceVoltage() {
switch (this.referenceVoltageType) {
case ADCReference.AVCC:
return this.avcc;
case ADCReference.AREF:
return this.aref;
case ADCReference.Internal1V1:
return 1.1;
case ADCReference.Internal2V56:
return 2.56;
default:
return this.avcc;
}
}
get sampleCycles() {
return this.conversionCycles * this.prescaler;
}
}
+54
View File
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
const CLKPCE = 128;
export const clockConfig = {
CLKPR: 0x61,
};
const prescalers = [
1, 2, 4, 8, 16, 32, 64, 128, 256,
// The following values are "reserved" according to the datasheet, so we measured
// with a scope to figure them out (on ATmega328p)
2, 4, 8, 16, 32, 64, 128,
];
export class AVRClock {
constructor(cpu, baseFreqHz, config = clockConfig) {
this.cpu = cpu;
this.baseFreqHz = baseFreqHz;
this.config = config;
this.clockEnabledCycles = 0;
this.prescalerValue = 1;
this.cyclesDelta = 0;
this.cpu.writeHooks[this.config.CLKPR] = (clkpr) => {
if ((!this.clockEnabledCycles || this.clockEnabledCycles < cpu.cycles) && clkpr === CLKPCE) {
this.clockEnabledCycles = this.cpu.cycles + 4;
}
else if (this.clockEnabledCycles && this.clockEnabledCycles >= cpu.cycles) {
this.clockEnabledCycles = 0;
const index = clkpr & 0xf;
const oldPrescaler = this.prescalerValue;
this.prescalerValue = prescalers[index];
this.cpu.data[this.config.CLKPR] = index;
if (oldPrescaler !== this.prescalerValue) {
this.cyclesDelta =
(cpu.cycles + this.cyclesDelta) * (oldPrescaler / this.prescalerValue) - cpu.cycles;
}
}
return true;
};
}
get frequency() {
return this.baseFreqHz / this.prescalerValue;
}
get prescaler() {
return this.prescalerValue;
}
get timeNanos() {
return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e9;
}
get timeMicros() {
return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e6;
}
get timeMillis() {
return ((this.cpu.cycles + this.cyclesDelta) / this.frequency) * 1e3;
}
}
+114
View File
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
export class EEPROMMemoryBackend {
constructor(size) {
this.memory = new Uint8Array(size);
this.memory.fill(0xff);
}
readMemory(addr) {
return this.memory[addr];
}
writeMemory(addr, value) {
this.memory[addr] &= value;
}
eraseMemory(addr) {
this.memory[addr] = 0xff;
}
}
export const eepromConfig = {
eepromReadyInterrupt: 0x2c,
EECR: 0x3f,
EEDR: 0x40,
EEARL: 0x41,
EEARH: 0x42,
eraseCycles: 28800, // 1.8ms at 16MHz
writeCycles: 28800, // 1.8ms at 16MHz
};
const EERE = 1 << 0;
const EEPE = 1 << 1;
const EEMPE = 1 << 2;
const EERIE = 1 << 3;
const EEPM0 = 1 << 4;
const EEPM1 = 1 << 5;
const EECR_WRITE_MASK = EEPE | EEMPE | EERIE | EEPM0 | EEPM1;
export class AVREEPROM {
constructor(cpu, backend, config = eepromConfig) {
this.cpu = cpu;
this.backend = backend;
this.config = config;
/**
* Used to keep track on the last write to EEMPE. From the datasheet:
* The EEMPE bit determines whether setting EEPE to one causes the EEPROM to be written.
* When EEMPE is set, setting EEPE within four clock cycles will write data to the EEPROM
* at the selected address If EEMPE is zero, setting EEPE will have no effect.
*/
this.writeEnabledCycles = 0;
this.writeCompleteCycles = 0;
// Interrupts
this.EER = {
address: this.config.eepromReadyInterrupt,
flagRegister: this.config.EECR,
flagMask: EEPE,
enableRegister: this.config.EECR,
enableMask: EERIE,
constant: true,
inverseFlag: true,
};
this.cpu.writeHooks[this.config.EECR] = (eecr) => {
const { EEARH, EEARL, EECR, EEDR } = this.config;
const addr = (this.cpu.data[EEARH] << 8) | this.cpu.data[EEARL];
this.cpu.data[EECR] = (this.cpu.data[EECR] & ~EECR_WRITE_MASK) | (eecr & EECR_WRITE_MASK);
this.cpu.updateInterruptEnable(this.EER, eecr);
if (eecr & EERE) {
this.cpu.clearInterrupt(this.EER);
}
if (eecr & EEMPE) {
const eempeCycles = 4;
this.writeEnabledCycles = this.cpu.cycles + eempeCycles;
this.cpu.addClockEvent(() => {
this.cpu.data[EECR] &= ~EEMPE;
}, eempeCycles);
}
// Read
if (eecr & EERE) {
this.cpu.data[EEDR] = this.backend.readMemory(addr);
// When the EEPROM is read, the CPU is halted for four cycles before the
// next instruction is executed.
this.cpu.cycles += 4;
return true;
}
// Write
if (eecr & EEPE) {
// If EEMPE is zero, setting EEPE will have no effect.
if (this.cpu.cycles >= this.writeEnabledCycles) {
this.cpu.data[EECR] &= ~EEPE;
return true;
}
// Check for write-in-progress
if (this.cpu.cycles < this.writeCompleteCycles) {
return true;
}
const eedr = this.cpu.data[EEDR];
this.writeCompleteCycles = this.cpu.cycles;
// Erase
if (!(eecr & EEPM1)) {
this.backend.eraseMemory(addr);
this.writeCompleteCycles += this.config.eraseCycles;
}
// Write
if (!(eecr & EEPM0)) {
this.backend.writeMemory(addr, eedr);
this.writeCompleteCycles += this.config.writeCycles;
}
this.cpu.data[EECR] |= EEPE;
this.cpu.addClockEvent(() => {
this.cpu.setInterruptFlag(this.EER);
}, this.writeCompleteCycles - this.cpu.cycles);
// When EEPE has been set, the CPU is halted for two cycles before the
// next instruction is executed.
this.cpu.cycles += 2;
}
return true;
};
}
}
+422
View File
@@ -0,0 +1,422 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
export const INT0 = {
EICR: 0x69,
EIMSK: 0x3d,
EIFR: 0x3c,
index: 0,
iscOffset: 0,
interrupt: 2,
};
export const INT1 = {
EICR: 0x69,
EIMSK: 0x3d,
EIFR: 0x3c,
index: 1,
iscOffset: 2,
interrupt: 4,
};
export const PCINT0 = {
PCIE: 0,
PCICR: 0x68,
PCIFR: 0x3b,
PCMSK: 0x6b,
pinChangeInterrupt: 6,
mask: 0xff,
offset: 0,
};
export const PCINT1 = {
PCIE: 1,
PCICR: 0x68,
PCIFR: 0x3b,
PCMSK: 0x6c,
pinChangeInterrupt: 8,
mask: 0xff,
offset: 0,
};
export const PCINT2 = {
PCIE: 2,
PCICR: 0x68,
PCIFR: 0x3b,
PCMSK: 0x6d,
pinChangeInterrupt: 10,
mask: 0xff,
offset: 0,
};
export const portAConfig = {
PIN: 0x20,
DDR: 0x21,
PORT: 0x22,
externalInterrupts: [],
};
export const portBConfig = {
PIN: 0x23,
DDR: 0x24,
PORT: 0x25,
// Interrupt settings
pinChange: PCINT0,
externalInterrupts: [],
};
export const portCConfig = {
PIN: 0x26,
DDR: 0x27,
PORT: 0x28,
// Interrupt settings
pinChange: PCINT1,
externalInterrupts: [],
};
export const portDConfig = {
PIN: 0x29,
DDR: 0x2a,
PORT: 0x2b,
// Interrupt settings
pinChange: PCINT2,
externalInterrupts: [null, null, INT0, INT1],
};
export const portEConfig = {
PIN: 0x2c,
DDR: 0x2d,
PORT: 0x2e,
externalInterrupts: [],
};
export const portFConfig = {
PIN: 0x2f,
DDR: 0x30,
PORT: 0x31,
externalInterrupts: [],
};
export const portGConfig = {
PIN: 0x32,
DDR: 0x33,
PORT: 0x34,
externalInterrupts: [],
};
export const portHConfig = {
PIN: 0x100,
DDR: 0x101,
PORT: 0x102,
externalInterrupts: [],
};
export const portJConfig = {
PIN: 0x103,
DDR: 0x104,
PORT: 0x105,
externalInterrupts: [],
};
export const portKConfig = {
PIN: 0x106,
DDR: 0x107,
PORT: 0x108,
externalInterrupts: [],
};
export const portLConfig = {
PIN: 0x109,
DDR: 0x10a,
PORT: 0x10b,
externalInterrupts: [],
};
export var PinState;
(function (PinState) {
PinState[PinState["Low"] = 0] = "Low";
PinState[PinState["High"] = 1] = "High";
PinState[PinState["Input"] = 2] = "Input";
PinState[PinState["InputPullUp"] = 3] = "InputPullUp";
})(PinState || (PinState = {}));
/* This mechanism allows timers to override specific GPIO pins */
export var PinOverrideMode;
(function (PinOverrideMode) {
PinOverrideMode[PinOverrideMode["None"] = 0] = "None";
PinOverrideMode[PinOverrideMode["Enable"] = 1] = "Enable";
PinOverrideMode[PinOverrideMode["Set"] = 2] = "Set";
PinOverrideMode[PinOverrideMode["Clear"] = 3] = "Clear";
PinOverrideMode[PinOverrideMode["Toggle"] = 4] = "Toggle";
})(PinOverrideMode || (PinOverrideMode = {}));
var InterruptMode;
(function (InterruptMode) {
InterruptMode[InterruptMode["LowLevel"] = 0] = "LowLevel";
InterruptMode[InterruptMode["Change"] = 1] = "Change";
InterruptMode[InterruptMode["FallingEdge"] = 2] = "FallingEdge";
InterruptMode[InterruptMode["RisingEdge"] = 3] = "RisingEdge";
})(InterruptMode || (InterruptMode = {}));
export class AVRIOPort {
constructor(cpu, portConfig) {
var _a, _b, _c, _d;
this.cpu = cpu;
this.portConfig = portConfig;
this.externalClockListeners = [];
this.listeners = [];
this.pinValue = 0;
this.overrideMask = 0xff;
this.overrideValue = 0;
this.lastValue = 0;
this.lastDdr = 0;
this.lastPin = 0;
this.openCollector = 0;
cpu.gpioPorts.add(this);
cpu.gpioByPort[portConfig.PORT] = this;
cpu.writeHooks[portConfig.DDR] = (value) => {
const portValue = cpu.data[portConfig.PORT];
cpu.data[portConfig.DDR] = value;
this.writeGpio(portValue, value);
this.updatePinRegister(value);
return true;
};
cpu.writeHooks[portConfig.PORT] = (value) => {
const ddrMask = cpu.data[portConfig.DDR];
cpu.data[portConfig.PORT] = value;
this.writeGpio(value, ddrMask);
this.updatePinRegister(ddrMask);
return true;
};
cpu.writeHooks[portConfig.PIN] = (value, oldValue, addr, mask) => {
// Writing to 1 PIN toggles PORT bits
const oldPortValue = cpu.data[portConfig.PORT];
const ddrMask = cpu.data[portConfig.DDR];
const portValue = oldPortValue ^ (value & mask);
cpu.data[portConfig.PORT] = portValue;
this.writeGpio(portValue, ddrMask);
this.updatePinRegister(ddrMask);
return true;
};
// External interrupts
const { externalInterrupts } = portConfig;
this.externalInts = externalInterrupts.map((externalConfig) => externalConfig
? {
address: externalConfig.interrupt,
flagRegister: externalConfig.EIFR,
flagMask: 1 << externalConfig.index,
enableRegister: externalConfig.EIMSK,
enableMask: 1 << externalConfig.index,
}
: null);
const EICR = new Set(externalInterrupts.map((item) => item === null || item === void 0 ? void 0 : item.EICR));
for (const EICRx of EICR) {
this.attachInterruptHook(EICRx || 0);
}
const EIMSK = (_b = (_a = externalInterrupts.find((item) => item && item.EIMSK)) === null || _a === void 0 ? void 0 : _a.EIMSK) !== null && _b !== void 0 ? _b : 0;
this.attachInterruptHook(EIMSK, 'mask');
const EIFR = (_d = (_c = externalInterrupts.find((item) => item && item.EIFR)) === null || _c === void 0 ? void 0 : _c.EIFR) !== null && _d !== void 0 ? _d : 0;
this.attachInterruptHook(EIFR, 'flag');
// Pin change interrupts
const { pinChange } = portConfig;
this.PCINT = pinChange
? {
address: pinChange.pinChangeInterrupt,
flagRegister: pinChange.PCIFR,
flagMask: 1 << pinChange.PCIE,
enableRegister: pinChange.PCICR,
enableMask: 1 << pinChange.PCIE,
}
: null;
if (pinChange) {
const { PCIFR, PCMSK } = pinChange;
cpu.writeHooks[PCIFR] = (value) => {
for (const gpio of this.cpu.gpioPorts) {
const { PCINT } = gpio;
if (PCINT) {
cpu.clearInterruptByFlag(PCINT, value);
}
}
return true;
};
cpu.writeHooks[PCMSK] = (value) => {
cpu.data[PCMSK] = value;
for (const gpio of this.cpu.gpioPorts) {
const { PCINT } = gpio;
if (PCINT) {
cpu.updateInterruptEnable(PCINT, value);
}
}
return true;
};
}
}
addListener(listener) {
this.listeners.push(listener);
}
removeListener(listener) {
this.listeners = this.listeners.filter((l) => l !== listener);
}
/**
* Get the state of a given GPIO pin
*
* @param index Pin index to return from 0 to 7
* @returns PinState.Low or PinState.High if the pin is set to output, PinState.Input if the pin is set
* to input, and PinState.InputPullUp if the pin is set to input and the internal pull-up resistor has
* been enabled.
*/
pinState(index) {
const ddr = this.cpu.data[this.portConfig.DDR];
const port = this.cpu.data[this.portConfig.PORT];
const bitMask = 1 << index;
const openState = port & bitMask ? PinState.InputPullUp : PinState.Input;
const highValue = this.openCollector & bitMask ? openState : PinState.High;
if (ddr & bitMask) {
return this.lastValue & bitMask ? highValue : PinState.Low;
}
else {
return openState;
}
}
/**
* Sets the input value for the given pin. This is the value that
* will be returned when reading from the PIN register.
*/
setPin(index, value) {
const bitMask = 1 << index;
this.pinValue &= ~bitMask;
if (value) {
this.pinValue |= bitMask;
}
this.updatePinRegister(this.cpu.data[this.portConfig.DDR]);
}
/**
* Internal method - do not call this directly!
* Used by the timer compare output units to override GPIO pins.
*/
timerOverridePin(pin, mode) {
const { cpu, portConfig } = this;
const pinMask = 1 << pin;
if (mode === PinOverrideMode.None) {
this.overrideMask |= pinMask;
this.overrideValue &= ~pinMask;
}
else {
this.overrideMask &= ~pinMask;
switch (mode) {
case PinOverrideMode.Enable:
this.overrideValue &= ~pinMask;
this.overrideValue |= cpu.data[portConfig.PORT] & pinMask;
break;
case PinOverrideMode.Set:
this.overrideValue |= pinMask;
break;
case PinOverrideMode.Clear:
this.overrideValue &= ~pinMask;
break;
case PinOverrideMode.Toggle:
this.overrideValue ^= pinMask;
break;
}
}
const ddrMask = cpu.data[portConfig.DDR];
this.writeGpio(cpu.data[portConfig.PORT], ddrMask);
this.updatePinRegister(ddrMask);
}
updatePinRegister(ddr) {
var _a, _b;
const newPin = (this.pinValue & ~ddr) | (this.lastValue & ddr);
this.cpu.data[this.portConfig.PIN] = newPin;
if (this.lastPin !== newPin) {
for (let index = 0; index < 8; index++) {
if ((newPin & (1 << index)) !== (this.lastPin & (1 << index))) {
const value = !!(newPin & (1 << index));
this.toggleInterrupt(index, value);
(_b = (_a = this.externalClockListeners)[index]) === null || _b === void 0 ? void 0 : _b.call(_a, value);
}
}
this.lastPin = newPin;
}
}
toggleInterrupt(pin, risingEdge) {
const { cpu, portConfig, externalInts, PCINT } = this;
const { externalInterrupts, pinChange } = portConfig;
const externalConfig = externalInterrupts[pin];
const external = externalInts[pin];
if (external && externalConfig) {
const { EIMSK, index, EICR, iscOffset } = externalConfig;
if (cpu.data[EIMSK] & (1 << index)) {
const configuration = (cpu.data[EICR] >> iscOffset) & 0x3;
let generateInterrupt = false;
external.constant = false;
switch (configuration) {
case InterruptMode.LowLevel:
generateInterrupt = !risingEdge;
external.constant = true;
break;
case InterruptMode.Change:
generateInterrupt = true;
break;
case InterruptMode.FallingEdge:
generateInterrupt = !risingEdge;
break;
case InterruptMode.RisingEdge:
generateInterrupt = risingEdge;
break;
}
if (generateInterrupt) {
cpu.setInterruptFlag(external);
}
else if (external.constant) {
cpu.clearInterrupt(external, true);
}
}
}
if (pinChange && PCINT && pinChange.mask & (1 << pin)) {
const { PCMSK } = pinChange;
if (cpu.data[PCMSK] & (1 << (pin + pinChange.offset))) {
cpu.setInterruptFlag(PCINT);
}
}
}
attachInterruptHook(register, registerType = 'other') {
if (!register) {
return;
}
const { cpu } = this;
cpu.writeHooks[register] = (value) => {
if (registerType !== 'flag') {
cpu.data[register] = value;
}
for (const gpio of cpu.gpioPorts) {
for (const external of gpio.externalInts) {
if (external && registerType === 'mask') {
cpu.updateInterruptEnable(external, value);
}
if (external && !external.constant && registerType === 'flag') {
cpu.clearInterruptByFlag(external, value);
}
}
gpio.checkExternalInterrupts();
}
return true;
};
}
checkExternalInterrupts() {
const { cpu } = this;
const { externalInterrupts } = this.portConfig;
for (let pin = 0; pin < 8; pin++) {
const external = externalInterrupts[pin];
if (!external) {
continue;
}
const pinValue = !!(this.lastPin & (1 << pin));
const { EIFR, EIMSK, index, EICR, iscOffset, interrupt } = external;
if (!(cpu.data[EIMSK] & (1 << index)) || pinValue) {
continue;
}
const configuration = (cpu.data[EICR] >> iscOffset) & 0x3;
if (configuration === InterruptMode.LowLevel) {
cpu.queueInterrupt({
address: interrupt,
flagRegister: EIFR,
flagMask: 1 << index,
enableRegister: EIMSK,
enableMask: 1 << index,
constant: true,
});
}
}
}
writeGpio(value, ddr) {
const newValue = (((value & this.overrideMask) | this.overrideValue) & ddr) | (value & ~ddr);
const prevValue = this.lastValue;
if (newValue !== prevValue || ddr !== this.lastDdr) {
this.lastValue = newValue;
this.lastDdr = ddr;
for (const listener of this.listeners) {
listener(newValue, prevValue);
}
}
}
}
+130
View File
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
// Register bits:
const SPCR_SPIE = 0x80; // SPI Interrupt Enable
const SPCR_SPE = 0x40; // SPI Enable
const SPCR_DORD = 0x20; // Data Order
const SPCR_MSTR = 0x10; // Master/Slave Select
const SPCR_CPOL = 0x8; // Clock Polarity
const SPCR_CPHA = 0x4; // Clock Phase
const SPCR_SPR1 = 0x2; // SPI Clock Rate Select 1
const SPCR_SPR0 = 0x1; // SPI Clock Rate Select 0
const SPSR_SPR_MASK = SPCR_SPR1 | SPCR_SPR0;
const SPSR_SPIF = 0x80; // SPI Interrupt Flag
const SPSR_WCOL = 0x40; // Write COLlision Flag
const SPSR_SPI2X = 0x1; // Double SPI Speed Bit
export const spiConfig = {
spiInterrupt: 0x22,
SPCR: 0x4c,
SPSR: 0x4d,
SPDR: 0x4e,
};
const bitsPerByte = 8;
export class AVRSPI {
constructor(cpu, config, freqHz) {
this.cpu = cpu;
this.config = config;
this.freqHz = freqHz;
/** @deprecated Use onByte() instead */
this.onTransfer = () => 0;
/**
* SPI byte transfer callback. Invoked whenever the user code starts an SPI transaction.
* You can override this with your own SPI handler logic.
*
* The callback receives a argument: the byte sent over the SPI MOSI line.
* It should call `completeTransfer()` within `transferCycles` CPU cycles.
*/
this.onByte = (value) => {
const valueIn = this.onTransfer(value);
this.cpu.addClockEvent(() => this.completeTransfer(valueIn), this.transferCycles);
};
this.transmissionActive = false;
// Interrupts
this.SPI = {
address: this.config.spiInterrupt,
flagRegister: this.config.SPSR,
flagMask: SPSR_SPIF,
enableRegister: this.config.SPCR,
enableMask: SPCR_SPIE,
};
const { SPCR, SPSR, SPDR } = config;
cpu.writeHooks[SPDR] = (value) => {
if (!(cpu.data[SPCR] & SPCR_SPE)) {
// SPI not enabled, ignore write
return;
}
// Write collision
if (this.transmissionActive) {
cpu.data[SPSR] |= SPSR_WCOL;
return true;
}
// Clear write collision / interrupt flags
cpu.data[SPSR] &= ~SPSR_WCOL;
this.cpu.clearInterrupt(this.SPI);
this.transmissionActive = true;
this.onByte(value);
return true;
};
cpu.writeHooks[SPCR] = (value) => {
this.cpu.updateInterruptEnable(this.SPI, value);
};
cpu.writeHooks[SPSR] = (value) => {
this.cpu.data[SPSR] = value;
this.cpu.clearInterruptByFlag(this.SPI, value);
};
}
reset() {
this.transmissionActive = false;
}
/**
* Completes an SPI transaction. Call this method only from the `onByte` callback.
*
* @param receivedByte Byte read from the SPI MISO line.
*/
completeTransfer(receivedByte) {
const { SPDR } = this.config;
this.cpu.data[SPDR] = receivedByte;
this.cpu.setInterruptFlag(this.SPI);
this.transmissionActive = false;
}
get isMaster() {
return this.cpu.data[this.config.SPCR] & SPCR_MSTR ? true : false;
}
get dataOrder() {
return this.cpu.data[this.config.SPCR] & SPCR_DORD ? 'lsbFirst' : 'msbFirst';
}
get spiMode() {
const CPHA = this.cpu.data[this.config.SPCR] & SPCR_CPHA;
const CPOL = this.cpu.data[this.config.SPCR] & SPCR_CPOL;
return ((CPHA ? 2 : 0) | (CPOL ? 1 : 0));
}
/**
* The clock divider is only relevant for Master mode
*/
get clockDivider() {
const base = this.cpu.data[this.config.SPSR] & SPSR_SPI2X ? 2 : 4;
switch (this.cpu.data[this.config.SPCR] & SPSR_SPR_MASK) {
case 0b00:
return base;
case 0b01:
return base * 4;
case 0b10:
return base * 16;
case 0b11:
return base * 32;
}
// We should never get here:
throw new Error('Invalid divider value!');
}
/** Number of cycles to complete a single byte SPI transaction */
get transferCycles() {
return this.clockDivider * bitsPerByte;
}
/**
* The SPI freqeuncy is only relevant to Master mode.
* In slave mode, the frequency can be as high as F(osc) / 4.
*/
get spiFrequency() {
return this.freqHz / this.clockDivider;
}
}
+343
View File
@@ -0,0 +1,343 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
import { PinOverrideMode } from './gpio.js';
// TCCR1 bits
const CTC1 = 1 << 7;
const PWM1A = 1 << 6;
const CS_MASK = 0x0f;
// GTCCR bits
const PWM1B_BIT = 1 << 6;
const FOC1B = 1 << 3;
const FOC1A = 1 << 2;
const PSR1 = 1 << 1;
export const attinyTimer1Config = {
TCCR1: 0x50,
GTCCR: 0x4c,
TCNT1: 0x4f,
OCR1A: 0x4e,
OCR1B: 0x4b,
OCR1C: 0x4d,
TIFR: 0x58,
TIMSK: 0x59,
ovfInterrupt: 0x04,
compAInterrupt: 0x03,
compBInterrupt: 0x09,
TOV1: 1 << 2,
OCF1A: 1 << 6,
OCF1B: 1 << 5,
TOIE1: 1 << 2,
OCIE1A: 1 << 6,
OCIE1B: 1 << 5,
compPortB: 0x38,
compPinA: 1, // PB1
compPinB: 4, // PB4
dividers: {
0: 0,
1: 1,
2: 2,
3: 4,
4: 8,
5: 16,
6: 32,
7: 64,
8: 128,
9: 256,
10: 512,
11: 1024,
12: 2048,
13: 4096,
14: 8192,
15: 16384,
},
};
export class ATtinyTimer1 {
constructor(cpu, config) {
this.cpu = cpu;
this.config = config;
this.lastCycle = 0;
this.tcnt = 0;
this.tcntNext = 0;
this.tcntUpdated = false;
this.ocrA = 0;
this.ocrB = 0;
this.ocrC = 0;
this.divider = 0;
this.updateDivider = false;
this.countingUp = true;
this.OVF = {
address: this.config.ovfInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.TOV1,
enableRegister: this.config.TIMSK,
enableMask: this.config.TOIE1,
};
this.OCFA = {
address: this.config.compAInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.OCF1A,
enableRegister: this.config.TIMSK,
enableMask: this.config.OCIE1A,
};
this.OCFB = {
address: this.config.compBInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.OCF1B,
enableRegister: this.config.TIMSK,
enableMask: this.config.OCIE1B,
};
this.count = (reschedule = true) => {
var _a;
const { divider, lastCycle, cpu } = this;
const { cycles } = cpu;
const delta = cycles - lastCycle;
if (divider && delta >= divider) {
const counterDelta = Math.floor(delta / divider);
this.lastCycle += counterDelta * divider;
const val = this.tcnt;
const top = this.TOP;
const phasePwm = (this.pwmA || this.pwmB) && !this.ctcMode;
const newVal = phasePwm
? this.phasePwmCount(val, counterDelta)
: (val + counterDelta) % (top + 1);
const overflow = val + counterDelta > top;
if (!this.tcntUpdated) {
this.tcnt = newVal;
if (!phasePwm) {
this.timerUpdated(newVal, val);
}
}
if (!phasePwm && overflow) {
cpu.setInterruptFlag(this.OVF);
}
}
if (this.tcntUpdated) {
this.tcnt = this.tcntNext;
this.tcntUpdated = false;
}
if (this.updateDivider) {
const cs = this.CS;
const newDivider = (_a = this.config.dividers[cs]) !== null && _a !== void 0 ? _a : 0;
this.lastCycle = newDivider ? this.cpu.cycles : 0;
this.updateDivider = false;
this.divider = newDivider;
if (newDivider) {
cpu.addClockEvent(this.count, this.lastCycle + newDivider - cpu.cycles);
}
return;
}
if (reschedule && divider) {
cpu.addClockEvent(this.count, this.lastCycle + divider - cpu.cycles);
}
};
const { TCCR1, GTCCR, TCNT1, OCR1A, OCR1B, OCR1C, TIFR, TIMSK } = config;
cpu.readHooks[TCNT1] = () => {
this.count(false);
return (cpu.data[TCNT1] = this.tcnt & 0xff);
};
cpu.writeHooks[TCNT1] = (value) => {
this.tcntNext = value;
this.countingUp = true;
this.tcntUpdated = true;
cpu.updateClockEvent(this.count, 0);
if (this.divider) {
this.timerUpdated(this.tcntNext, this.tcntNext);
}
};
cpu.writeHooks[OCR1A] = (value) => {
this.ocrA = value;
};
cpu.writeHooks[OCR1B] = (value) => {
this.ocrB = value;
};
cpu.writeHooks[OCR1C] = (value) => {
this.ocrC = value;
};
cpu.writeHooks[TCCR1] = (value) => {
cpu.data[TCCR1] = value;
this.updateDivider = true;
cpu.clearClockEvent(this.count);
cpu.addClockEvent(this.count, 0);
this.updateCompConfig();
return true;
};
// GTCCR is shared with Timer0 (PSR0) — chain with existing hook
const prevGtccrHook = cpu.writeHooks[GTCCR];
cpu.writeHooks[GTCCR] = (value, oldValue, addr, mask) => {
if (value & FOC1A) {
this.forceCompare('A');
}
if (value & FOC1B) {
this.forceCompare('B');
}
if (value & PSR1) {
this.lastCycle = this.cpu.cycles;
}
value &= ~(FOC1A | FOC1B | PSR1);
if (prevGtccrHook) {
prevGtccrHook(value, oldValue, addr, mask);
}
else {
cpu.data[GTCCR] = value;
}
this.updateCompConfig();
return true;
};
// TIFR/TIMSK are shared with Timer0 — chain with existing hooks
const prevTifrHook = cpu.writeHooks[TIFR];
cpu.writeHooks[TIFR] = (value, oldValue, addr, mask) => {
if (prevTifrHook) {
prevTifrHook(value, oldValue, addr, mask);
}
else {
cpu.data[TIFR] = value;
}
cpu.clearInterruptByFlag(this.OVF, value);
cpu.clearInterruptByFlag(this.OCFA, value);
cpu.clearInterruptByFlag(this.OCFB, value);
return true;
};
const prevTimskHook = cpu.writeHooks[TIMSK];
cpu.writeHooks[TIMSK] = (value, oldValue, addr, mask) => {
if (prevTimskHook) {
prevTimskHook(value, oldValue, addr, mask);
}
cpu.updateInterruptEnable(this.OVF, value);
cpu.updateInterruptEnable(this.OCFA, value);
cpu.updateInterruptEnable(this.OCFB, value);
};
}
get tccr1() {
return this.cpu.data[this.config.TCCR1];
}
get gtccr() {
return this.cpu.data[this.config.GTCCR];
}
get CS() {
return this.tccr1 & CS_MASK;
}
get ctcMode() {
return !!(this.tccr1 & CTC1);
}
get pwmA() {
return !!(this.tccr1 & PWM1A);
}
get pwmB() {
return !!(this.gtccr & PWM1B_BIT);
}
get comA() {
return (this.tccr1 >> 4) & 0x3;
}
get comB() {
return (this.gtccr >> 4) & 0x3;
}
/** TOP = OCR1C in CTC/PWM modes, 0xFF in Normal mode */
get TOP() {
if (this.ctcMode || this.pwmA || this.pwmB) {
return this.ocrC;
}
return 0xff;
}
phasePwmCount(value, delta) {
const top = this.TOP;
while (delta > 0) {
if (this.countingUp) {
value++;
if (value >= top) {
value = top;
this.countingUp = false;
}
}
else {
value--;
if (value <= 0) {
value = 0;
this.countingUp = true;
this.cpu.setInterruptFlag(this.OVF);
}
}
if (!this.tcntUpdated) {
if (value === this.ocrA) {
this.cpu.setInterruptFlag(this.OCFA);
this.updateCompPinPwm('A');
}
if (value === this.ocrB) {
this.cpu.setInterruptFlag(this.OCFB);
this.updateCompPinPwm('B');
}
}
delta--;
}
return value & 0xff;
}
timerUpdated(value, prevValue) {
const { ocrA, ocrB } = this;
const overflow = prevValue > value;
if (((prevValue < ocrA || overflow) && value >= ocrA) || (prevValue < ocrA && overflow)) {
this.cpu.setInterruptFlag(this.OCFA);
if (this.comA && !this.pwmA) {
this.updateCompPinNonPwm('A');
}
}
if (((prevValue < ocrB || overflow) && value >= ocrB) || (prevValue < ocrB && overflow)) {
this.cpu.setInterruptFlag(this.OCFB);
if (this.comB && !this.pwmB) {
this.updateCompPinNonPwm('B');
}
}
}
forceCompare(channel) {
if (channel === 'A' && !this.pwmA && this.comA) {
this.updateCompPinNonPwm('A');
}
else if (channel === 'B' && !this.pwmB && this.comB) {
this.updateCompPinNonPwm('B');
}
}
updateCompPinNonPwm(channel) {
var _a;
const com = channel === 'A' ? this.comA : this.comB;
const pin = channel === 'A' ? this.config.compPinA : this.config.compPinB;
let mode;
switch (com) {
case 1:
mode = PinOverrideMode.Toggle;
break;
case 2:
mode = PinOverrideMode.Clear;
break;
case 3:
mode = PinOverrideMode.Set;
break;
default:
return;
}
(_a = this.cpu.gpioByPort[this.config.compPortB]) === null || _a === void 0 ? void 0 : _a.timerOverridePin(pin, mode);
}
updateCompPinPwm(channel) {
var _a;
const com = channel === 'A' ? this.comA : this.comB;
const pin = channel === 'A' ? this.config.compPinA : this.config.compPinB;
const invertingMode = com === 3;
const isSet = this.countingUp === invertingMode;
let mode;
switch (com) {
case 1:
mode = PinOverrideMode.Toggle;
break;
case 2:
case 3:
mode = isSet ? PinOverrideMode.Set : PinOverrideMode.Clear;
break;
default:
return;
}
(_a = this.cpu.gpioByPort[this.config.compPortB]) === null || _a === void 0 ? void 0 : _a.timerOverridePin(pin, mode);
}
updateCompConfig() {
const port = this.cpu.gpioByPort[this.config.compPortB];
if (!port)
return;
port.timerOverridePin(this.config.compPinA, this.comA ? PinOverrideMode.Enable : PinOverrideMode.None);
port.timerOverridePin(this.config.compPinB, this.comB ? PinOverrideMode.Enable : PinOverrideMode.None);
}
}
+595
View File
@@ -0,0 +1,595 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
import { PinOverrideMode, portBConfig, portDConfig } from './gpio.js';
const timer01Dividers = {
0: 0,
1: 1,
2: 8,
3: 64,
4: 256,
5: 1024,
6: 0, // External clock - see ExternalClockMode
7: 0, // Ditto
};
var ExternalClockMode;
(function (ExternalClockMode) {
ExternalClockMode[ExternalClockMode["FallingEdge"] = 6] = "FallingEdge";
ExternalClockMode[ExternalClockMode["RisingEdge"] = 7] = "RisingEdge";
})(ExternalClockMode || (ExternalClockMode = {}));
/** These are differnet for some devices (e.g. ATtiny85) */
const defaultTimerBits = {
// TIFR bits
TOV: 1,
OCFA: 2,
OCFB: 4,
OCFC: 0, // Unused
// TIMSK bits
TOIE: 1,
OCIEA: 2,
OCIEB: 4,
OCIEC: 0, // Unused
};
export const timer0Config = Object.assign({ bits: 8, captureInterrupt: 0, compAInterrupt: 0x1c, compBInterrupt: 0x1e, compCInterrupt: 0, ovfInterrupt: 0x20, TIFR: 0x35, OCRA: 0x47, OCRB: 0x48, OCRC: 0, ICR: 0, TCNT: 0x46, TCCRA: 0x44, TCCRB: 0x45, TCCRC: 0, TIMSK: 0x6e, dividers: timer01Dividers, compPortA: portDConfig.PORT, compPinA: 6, compPortB: portDConfig.PORT, compPinB: 5, compPortC: 0, compPinC: 0, externalClockPort: portDConfig.PORT, externalClockPin: 4 }, defaultTimerBits);
export const timer1Config = Object.assign({ bits: 16, captureInterrupt: 0x14, compAInterrupt: 0x16, compBInterrupt: 0x18, compCInterrupt: 0, ovfInterrupt: 0x1a, TIFR: 0x36, OCRA: 0x88, OCRB: 0x8a, OCRC: 0, ICR: 0x86, TCNT: 0x84, TCCRA: 0x80, TCCRB: 0x81, TCCRC: 0x82, TIMSK: 0x6f, dividers: timer01Dividers, compPortA: portBConfig.PORT, compPinA: 1, compPortB: portBConfig.PORT, compPinB: 2, compPortC: 0, compPinC: 0, externalClockPort: portDConfig.PORT, externalClockPin: 5 }, defaultTimerBits);
export const timer2Config = Object.assign({ bits: 8, captureInterrupt: 0, compAInterrupt: 0x0e, compBInterrupt: 0x10, compCInterrupt: 0, ovfInterrupt: 0x12, TIFR: 0x37, OCRA: 0xb3, OCRB: 0xb4, OCRC: 0, ICR: 0, TCNT: 0xb2, TCCRA: 0xb0, TCCRB: 0xb1, TCCRC: 0, TIMSK: 0x70, dividers: {
0: 0,
1: 1,
2: 8,
3: 32,
4: 64,
5: 128,
6: 256,
7: 1024,
}, compPortA: portBConfig.PORT, compPinA: 3, compPortB: portDConfig.PORT, compPinB: 3, compPortC: 0, compPinC: 0, externalClockPort: 0, externalClockPin: 0 }, defaultTimerBits);
/* All the following types and constants are related to WGM (Waveform Generation Mode) bits: */
var TimerMode;
(function (TimerMode) {
TimerMode[TimerMode["Normal"] = 0] = "Normal";
TimerMode[TimerMode["PWMPhaseCorrect"] = 1] = "PWMPhaseCorrect";
TimerMode[TimerMode["CTC"] = 2] = "CTC";
TimerMode[TimerMode["FastPWM"] = 3] = "FastPWM";
TimerMode[TimerMode["PWMPhaseFrequencyCorrect"] = 4] = "PWMPhaseFrequencyCorrect";
TimerMode[TimerMode["Reserved"] = 5] = "Reserved";
})(TimerMode || (TimerMode = {}));
var TOVUpdateMode;
(function (TOVUpdateMode) {
TOVUpdateMode[TOVUpdateMode["Max"] = 0] = "Max";
TOVUpdateMode[TOVUpdateMode["Top"] = 1] = "Top";
TOVUpdateMode[TOVUpdateMode["Bottom"] = 2] = "Bottom";
})(TOVUpdateMode || (TOVUpdateMode = {}));
var OCRUpdateMode;
(function (OCRUpdateMode) {
OCRUpdateMode[OCRUpdateMode["Immediate"] = 0] = "Immediate";
OCRUpdateMode[OCRUpdateMode["Top"] = 1] = "Top";
OCRUpdateMode[OCRUpdateMode["Bottom"] = 2] = "Bottom";
})(OCRUpdateMode || (OCRUpdateMode = {}));
const TopOCRA = 1;
const TopICR = 2;
// Enable Toggle mode for OCxA in PWM Wave Generation mode
const OCToggle = 1;
const { Normal, PWMPhaseCorrect, CTC, FastPWM, Reserved, PWMPhaseFrequencyCorrect } = TimerMode;
const wgmModes8Bit = [
/*0*/ [Normal, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*1*/ [PWMPhaseCorrect, 0xff, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
/*2*/ [CTC, TopOCRA, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*3*/ [FastPWM, 0xff, OCRUpdateMode.Bottom, TOVUpdateMode.Max, 0],
/*4*/ [Reserved, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*5*/ [PWMPhaseCorrect, TopOCRA, OCRUpdateMode.Top, TOVUpdateMode.Bottom, OCToggle],
/*6*/ [Reserved, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*7*/ [FastPWM, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Top, OCToggle],
];
// Table 16-4 in the datasheet
const wgmModes16Bit = [
/*0 */ [Normal, 0xffff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*1 */ [PWMPhaseCorrect, 0x00ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
/*2 */ [PWMPhaseCorrect, 0x01ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
/*3 */ [PWMPhaseCorrect, 0x03ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
/*4 */ [CTC, TopOCRA, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*5 */ [FastPWM, 0x00ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top, 0],
/*6 */ [FastPWM, 0x01ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top, 0],
/*7 */ [FastPWM, 0x03ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top, 0],
/*8 */ [PWMPhaseFrequencyCorrect, TopICR, OCRUpdateMode.Bottom, TOVUpdateMode.Bottom, 0],
/*9 */ [PWMPhaseFrequencyCorrect, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Bottom, OCToggle],
/*10*/ [PWMPhaseCorrect, TopICR, OCRUpdateMode.Top, TOVUpdateMode.Bottom, 0],
/*11*/ [PWMPhaseCorrect, TopOCRA, OCRUpdateMode.Top, TOVUpdateMode.Bottom, OCToggle],
/*12*/ [CTC, TopICR, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*13*/ [Reserved, 0xffff, OCRUpdateMode.Immediate, TOVUpdateMode.Max, 0],
/*14*/ [FastPWM, TopICR, OCRUpdateMode.Bottom, TOVUpdateMode.Top, OCToggle],
/*15*/ [FastPWM, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Top, OCToggle],
];
function compToOverride(comp) {
switch (comp) {
case 1:
return PinOverrideMode.Toggle;
case 2:
return PinOverrideMode.Clear;
case 3:
return PinOverrideMode.Set;
default:
return PinOverrideMode.Enable;
}
}
// Force Output Compare (FOC) bits
const FOCA = 1 << 7;
const FOCB = 1 << 6;
const FOCC = 1 << 5;
export class AVRTimer {
constructor(cpu, config) {
this.cpu = cpu;
this.config = config;
this.MAX = this.config.bits === 16 ? 0xffff : 0xff;
this.lastCycle = 0;
this.ocrA = 0;
this.nextOcrA = 0;
this.ocrB = 0;
this.nextOcrB = 0;
this.hasOCRC = this.config.OCRC > 0;
this.ocrC = 0;
this.nextOcrC = 0;
this.ocrUpdateMode = OCRUpdateMode.Immediate;
this.tovUpdateMode = TOVUpdateMode.Max;
this.icr = 0; // only for 16-bit timers
this.tcnt = 0;
this.tcntNext = 0;
this.tcntUpdated = false;
this.updateDivider = false;
this.countingUp = true;
this.divider = 0;
this.externalClockRisingEdge = false;
// This is the temporary register used to access 16-bit registers (section 16.3 of the datasheet)
this.highByteTemp = 0;
// Interrupts
this.OVF = {
address: this.config.ovfInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.TOV,
enableRegister: this.config.TIMSK,
enableMask: this.config.TOIE,
};
this.OCFA = {
address: this.config.compAInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.OCFA,
enableRegister: this.config.TIMSK,
enableMask: this.config.OCIEA,
};
this.OCFB = {
address: this.config.compBInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.OCFB,
enableRegister: this.config.TIMSK,
enableMask: this.config.OCIEB,
};
this.OCFC = {
address: this.config.compCInterrupt,
flagRegister: this.config.TIFR,
flagMask: this.config.OCFC,
enableRegister: this.config.TIMSK,
enableMask: this.config.OCIEC,
};
this.count = (reschedule = true, external = false) => {
const { divider, lastCycle, cpu } = this;
const { cycles } = cpu;
const delta = cycles - lastCycle;
if ((divider && delta >= divider) || external) {
const counterDelta = external ? 1 : Math.floor(delta / divider);
this.lastCycle += counterDelta * divider;
const val = this.tcnt;
const { timerMode, TOP } = this;
const phasePwm = timerMode === PWMPhaseCorrect || timerMode === PWMPhaseFrequencyCorrect;
const newVal = phasePwm
? this.phasePwmCount(val, counterDelta)
: (val + counterDelta) % (TOP + 1);
const overflow = val + counterDelta > TOP;
// A CPU write overrides (has priority over) all counter clear or count operations.
if (!this.tcntUpdated) {
this.tcnt = newVal;
if (!phasePwm) {
this.timerUpdated(newVal, val);
}
}
if (!phasePwm) {
if (timerMode === FastPWM && overflow) {
const { compA, compB } = this;
if (compA) {
this.updateCompPin(compA, 'A', true);
}
if (compB) {
this.updateCompPin(compB, 'B', true);
}
}
if (this.ocrUpdateMode == OCRUpdateMode.Bottom && overflow) {
// OCRUpdateMode.Top only occurs in Phase Correct modes, handled by phasePwmCount()
this.ocrA = this.nextOcrA;
this.ocrB = this.nextOcrB;
this.ocrC = this.nextOcrC;
}
// OCRUpdateMode.Bottom only occurs in Phase Correct modes, handled by phasePwmCount().
// Thus we only handle TOVUpdateMode.Top or TOVUpdateMode.Max here.
if (overflow && (this.tovUpdateMode == TOVUpdateMode.Top || TOP === this.MAX)) {
cpu.setInterruptFlag(this.OVF);
}
}
}
if (this.tcntUpdated) {
this.tcnt = this.tcntNext;
this.tcntUpdated = false;
if ((this.tcnt === 0 && this.ocrUpdateMode === OCRUpdateMode.Bottom) ||
(this.tcnt === this.TOP && this.ocrUpdateMode === OCRUpdateMode.Top)) {
this.ocrA = this.nextOcrA;
this.ocrB = this.nextOcrB;
this.ocrC = this.nextOcrC;
}
}
if (this.updateDivider) {
const { CS } = this;
const { externalClockPin } = this.config;
const newDivider = this.config.dividers[CS];
this.lastCycle = newDivider ? this.cpu.cycles : 0;
this.updateDivider = false;
this.divider = newDivider;
if (this.config.externalClockPort && !this.externalClockPort) {
this.externalClockPort = this.cpu.gpioByPort[this.config.externalClockPort];
}
if (this.externalClockPort) {
this.externalClockPort.externalClockListeners[externalClockPin] = null;
}
if (newDivider) {
cpu.addClockEvent(this.count, this.lastCycle + newDivider - cpu.cycles);
}
else if (this.externalClockPort &&
(CS === ExternalClockMode.FallingEdge || CS === ExternalClockMode.RisingEdge)) {
this.externalClockPort.externalClockListeners[externalClockPin] =
this.externalClockCallback;
this.externalClockRisingEdge = CS === ExternalClockMode.RisingEdge;
}
return;
}
if (reschedule && divider) {
cpu.addClockEvent(this.count, this.lastCycle + divider - cpu.cycles);
}
};
this.externalClockCallback = (value) => {
if (value === this.externalClockRisingEdge) {
this.count(false, true);
}
};
this.updateWGMConfig();
this.cpu.readHooks[config.TCNT] = (addr) => {
this.count(false);
if (this.config.bits === 16) {
this.cpu.data[addr + 1] = this.tcnt >> 8;
}
return (this.cpu.data[addr] = this.tcnt & 0xff);
};
this.cpu.writeHooks[config.TCNT] = (value) => {
this.tcntNext = (this.highByteTemp << 8) | value;
this.countingUp = true;
this.tcntUpdated = true;
this.cpu.updateClockEvent(this.count, 0);
if (this.divider) {
this.timerUpdated(this.tcntNext, this.tcntNext);
}
};
this.cpu.writeHooks[config.OCRA] = (value) => {
this.nextOcrA = (this.highByteTemp << 8) | value;
if (this.ocrUpdateMode === OCRUpdateMode.Immediate) {
this.ocrA = this.nextOcrA;
}
};
this.cpu.writeHooks[config.OCRB] = (value) => {
this.nextOcrB = (this.highByteTemp << 8) | value;
if (this.ocrUpdateMode === OCRUpdateMode.Immediate) {
this.ocrB = this.nextOcrB;
}
};
if (this.hasOCRC) {
this.cpu.writeHooks[config.OCRC] = (value) => {
this.nextOcrC = (this.highByteTemp << 8) | value;
if (this.ocrUpdateMode === OCRUpdateMode.Immediate) {
this.ocrC = this.nextOcrC;
}
};
}
if (this.config.bits === 16) {
this.cpu.writeHooks[config.ICR] = (value) => {
this.icr = (this.highByteTemp << 8) | value;
};
const updateTempRegister = (value) => {
this.highByteTemp = value;
};
const updateOCRHighRegister = (value, old, addr) => {
this.highByteTemp = value & (this.ocrMask >> 8);
cpu.data[addr] = this.highByteTemp;
return true;
};
this.cpu.writeHooks[config.TCNT + 1] = updateTempRegister;
this.cpu.writeHooks[config.OCRA + 1] = updateOCRHighRegister;
this.cpu.writeHooks[config.OCRB + 1] = updateOCRHighRegister;
if (this.hasOCRC) {
this.cpu.writeHooks[config.OCRC + 1] = updateOCRHighRegister;
}
this.cpu.writeHooks[config.ICR + 1] = updateTempRegister;
}
cpu.writeHooks[config.TCCRA] = (value) => {
this.cpu.data[config.TCCRA] = value;
this.updateWGMConfig();
return true;
};
cpu.writeHooks[config.TCCRB] = (value) => {
if (!config.TCCRC) {
this.checkForceCompare(value);
value &= ~(FOCA | FOCB);
}
this.cpu.data[config.TCCRB] = value;
this.updateDivider = true;
this.cpu.clearClockEvent(this.count);
this.cpu.addClockEvent(this.count, 0);
this.updateWGMConfig();
return true;
};
if (config.TCCRC) {
cpu.writeHooks[config.TCCRC] = (value) => {
this.checkForceCompare(value);
};
}
cpu.writeHooks[config.TIFR] = (value) => {
this.cpu.data[config.TIFR] = value;
this.cpu.clearInterruptByFlag(this.OVF, value);
this.cpu.clearInterruptByFlag(this.OCFA, value);
this.cpu.clearInterruptByFlag(this.OCFB, value);
return true;
};
cpu.writeHooks[config.TIMSK] = (value) => {
this.cpu.updateInterruptEnable(this.OVF, value);
this.cpu.updateInterruptEnable(this.OCFA, value);
this.cpu.updateInterruptEnable(this.OCFB, value);
};
}
reset() {
this.divider = 0;
this.lastCycle = 0;
this.ocrA = 0;
this.nextOcrA = 0;
this.ocrB = 0;
this.nextOcrB = 0;
this.ocrC = 0;
this.nextOcrC = 0;
this.icr = 0;
this.tcnt = 0;
this.tcntNext = 0;
this.tcntUpdated = false;
this.countingUp = false;
this.updateDivider = true;
}
get TCCRA() {
return this.cpu.data[this.config.TCCRA];
}
get TCCRB() {
return this.cpu.data[this.config.TCCRB];
}
get TIMSK() {
return this.cpu.data[this.config.TIMSK];
}
get CS() {
return (this.TCCRB & 0x7);
}
get WGM() {
const mask = this.config.bits === 16 ? 0x18 : 0x8;
return ((this.TCCRB & mask) >> 1) | (this.TCCRA & 0x3);
}
get TOP() {
switch (this.topValue) {
case TopOCRA:
return this.ocrA;
case TopICR:
return this.icr;
default:
return this.topValue;
}
}
get ocrMask() {
switch (this.topValue) {
case TopOCRA:
case TopICR:
return 0xffff;
default:
return this.topValue;
}
}
/** Expose the raw value of TCNT, for use by the unit tests */
get debugTCNT() {
return this.tcnt;
}
updateWGMConfig() {
const { config, WGM } = this;
const wgmModes = config.bits === 16 ? wgmModes16Bit : wgmModes8Bit;
const TCCRA = this.cpu.data[config.TCCRA];
const [timerMode, topValue, ocrUpdateMode, tovUpdateMode, flags] = wgmModes[WGM];
this.timerMode = timerMode;
this.topValue = topValue;
this.ocrUpdateMode = ocrUpdateMode;
this.tovUpdateMode = tovUpdateMode;
const pwmMode = timerMode === FastPWM ||
timerMode === PWMPhaseCorrect ||
timerMode === PWMPhaseFrequencyCorrect;
const prevCompA = this.compA;
this.compA = ((TCCRA >> 6) & 0x3);
if (this.compA === 1 && pwmMode && !(flags & OCToggle)) {
this.compA = 0;
}
if (!!prevCompA !== !!this.compA) {
this.updateCompA(this.compA ? PinOverrideMode.Enable : PinOverrideMode.None);
}
const prevCompB = this.compB;
this.compB = ((TCCRA >> 4) & 0x3);
if (this.compB === 1 && pwmMode) {
this.compB = 0; // Reserved, according to the datasheet
}
if (!!prevCompB !== !!this.compB) {
this.updateCompB(this.compB ? PinOverrideMode.Enable : PinOverrideMode.None);
}
if (this.hasOCRC) {
const prevCompC = this.compC;
this.compC = ((TCCRA >> 2) & 0x3);
if (this.compC === 1 && pwmMode) {
this.compC = 0; // Reserved, according to the datasheet
}
if (!!prevCompC !== !!this.compC) {
this.updateCompC(this.compC ? PinOverrideMode.Enable : PinOverrideMode.None);
}
}
}
phasePwmCount(value, delta) {
const { ocrA, ocrB, ocrC, hasOCRC, TOP, MAX, tcntUpdated } = this;
if (!value && !TOP) {
delta = 0;
if (this.ocrUpdateMode === OCRUpdateMode.Top) {
this.ocrA = this.nextOcrA;
this.ocrB = this.nextOcrB;
this.ocrC = this.nextOcrC;
}
}
while (delta > 0) {
if (this.countingUp) {
value++;
if (value === TOP && !tcntUpdated) {
this.countingUp = false;
if (this.ocrUpdateMode === OCRUpdateMode.Top) {
this.ocrA = this.nextOcrA;
this.ocrB = this.nextOcrB;
this.ocrC = this.nextOcrC;
}
}
}
else {
value--;
if (!value && !tcntUpdated) {
this.countingUp = true;
this.cpu.setInterruptFlag(this.OVF);
if (this.ocrUpdateMode === OCRUpdateMode.Bottom) {
this.ocrA = this.nextOcrA;
this.ocrB = this.nextOcrB;
this.ocrC = this.nextOcrC;
}
}
}
if (!tcntUpdated) {
if (value === ocrA) {
this.cpu.setInterruptFlag(this.OCFA);
if (this.compA) {
this.updateCompPin(this.compA, 'A');
}
}
if (value === ocrB) {
this.cpu.setInterruptFlag(this.OCFB);
if (this.compB) {
this.updateCompPin(this.compB, 'B');
}
}
if (hasOCRC && value === ocrC) {
this.cpu.setInterruptFlag(this.OCFC);
if (this.compC) {
this.updateCompPin(this.compC, 'C');
}
}
}
delta--;
}
return value & MAX;
}
timerUpdated(value, prevValue) {
const { ocrA, ocrB, ocrC, hasOCRC } = this;
const overflow = prevValue > value;
if (((prevValue < ocrA || overflow) && value >= ocrA) || (prevValue < ocrA && overflow)) {
this.cpu.setInterruptFlag(this.OCFA);
if (this.compA) {
this.updateCompPin(this.compA, 'A');
}
}
if (((prevValue < ocrB || overflow) && value >= ocrB) || (prevValue < ocrB && overflow)) {
this.cpu.setInterruptFlag(this.OCFB);
if (this.compB) {
this.updateCompPin(this.compB, 'B');
}
}
if (hasOCRC &&
(((prevValue < ocrC || overflow) && value >= ocrC) || (prevValue < ocrC && overflow))) {
this.cpu.setInterruptFlag(this.OCFC);
if (this.compC) {
this.updateCompPin(this.compC, 'C');
}
}
}
checkForceCompare(value) {
if (this.timerMode == TimerMode.FastPWM ||
this.timerMode == TimerMode.PWMPhaseCorrect ||
this.timerMode == TimerMode.PWMPhaseFrequencyCorrect) {
// The FOCnA/FOCnB/FOCnC bits are only active when the WGMn3:0 bits specifies a non-PWM mode
return;
}
if (value & FOCA) {
this.updateCompPin(this.compA, 'A');
}
if (value & FOCB) {
this.updateCompPin(this.compB, 'B');
}
if (this.config.compPortC && value & FOCC) {
this.updateCompPin(this.compC, 'C');
}
}
updateCompPin(compValue, pinName, bottom = false) {
let newValue = PinOverrideMode.None;
const invertingMode = compValue === 3;
const isSet = this.countingUp === invertingMode;
switch (this.timerMode) {
case Normal:
case CTC:
newValue = compToOverride(compValue);
break;
case FastPWM:
if (compValue === 1) {
newValue = bottom ? PinOverrideMode.None : PinOverrideMode.Toggle;
}
else {
newValue = invertingMode !== bottom ? PinOverrideMode.Set : PinOverrideMode.Clear;
}
break;
case PWMPhaseCorrect:
case PWMPhaseFrequencyCorrect:
if (compValue === 1) {
newValue = PinOverrideMode.Toggle;
}
else {
newValue = isSet ? PinOverrideMode.Set : PinOverrideMode.Clear;
}
break;
}
if (newValue !== PinOverrideMode.None) {
if (pinName === 'A') {
this.updateCompA(newValue);
}
else if (pinName === 'B') {
this.updateCompB(newValue);
}
else {
this.updateCompC(newValue);
}
}
}
updateCompA(value) {
const { compPortA, compPinA } = this.config;
const port = this.cpu.gpioByPort[compPortA];
port === null || port === void 0 ? void 0 : port.timerOverridePin(compPinA, value);
}
updateCompB(value) {
const { compPortB, compPinB } = this.config;
const port = this.cpu.gpioByPort[compPortB];
port === null || port === void 0 ? void 0 : port.timerOverridePin(compPinB, value);
}
updateCompC(value) {
const { compPortC, compPinC } = this.config;
const port = this.cpu.gpioByPort[compPortC];
port === null || port === void 0 ? void 0 : port.timerOverridePin(compPinC, value);
}
}
+168
View File
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
/* eslint-disable @typescript-eslint/no-unused-vars */
// Register bits:
const TWCR_TWINT = 0x80; // TWI Interrupt Flag
const TWCR_TWEA = 0x40; // TWI Enable Acknowledge Bit
const TWCR_TWSTA = 0x20; // TWI START Condition Bit
const TWCR_TWSTO = 0x10; // TWI STOP Condition Bit
const TWCR_TWWC = 0x8; //TWI Write Collision Flag
const TWCR_TWEN = 0x4; // TWI Enable Bit
const TWCR_TWIE = 0x1; // TWI Interrupt Enable
const TWSR_TWS_MASK = 0xf8; // TWI Status
const TWSR_TWPS1 = 0x2; // TWI Prescaler Bits
const TWSR_TWPS0 = 0x1; // TWI Prescaler Bits
const TWSR_TWPS_MASK = TWSR_TWPS1 | TWSR_TWPS0; // TWI Prescaler mask
const TWAR_TWA_MASK = 0xfe; // TWI (Slave) Address Register
const TWAR_TWGCE = 0x1; // TWI General Call Recognition Enable Bit
const STATUS_BUS_ERROR = 0x0;
const STATUS_TWI_IDLE = 0xf8;
// Master states
const STATUS_START = 0x08;
const STATUS_REPEATED_START = 0x10;
const STATUS_SLAW_ACK = 0x18;
const STATUS_SLAW_NACK = 0x20;
const STATUS_DATA_SENT_ACK = 0x28;
const STATUS_DATA_SENT_NACK = 0x30;
const STATUS_DATA_LOST_ARBITRATION = 0x38;
const STATUS_SLAR_ACK = 0x40;
const STATUS_SLAR_NACK = 0x48;
const STATUS_DATA_RECEIVED_ACK = 0x50;
const STATUS_DATA_RECEIVED_NACK = 0x58;
// TODO: add slave states
/* eslint-enable @typescript-eslint/no-unused-vars */
export const twiConfig = {
twiInterrupt: 0x30,
TWBR: 0xb8,
TWSR: 0xb9,
TWAR: 0xba,
TWDR: 0xbb,
TWCR: 0xbc,
TWAMR: 0xbd,
};
// A simple TWI Event Handler that sends a NACK for all events
export class NoopTWIEventHandler {
constructor(twi) {
this.twi = twi;
}
start() {
this.twi.completeStart();
}
stop() {
this.twi.completeStop();
}
connectToSlave() {
this.twi.completeConnect(false);
}
writeByte() {
this.twi.completeWrite(false);
}
readByte() {
this.twi.completeRead(0xff);
}
}
export class AVRTWI {
constructor(cpu, config, freqHz) {
this.cpu = cpu;
this.config = config;
this.freqHz = freqHz;
this.eventHandler = new NoopTWIEventHandler(this);
this.busy = false;
// Interrupts
this.TWI = {
address: this.config.twiInterrupt,
flagRegister: this.config.TWCR,
flagMask: TWCR_TWINT,
enableRegister: this.config.TWCR,
enableMask: TWCR_TWIE,
};
this.updateStatus(STATUS_TWI_IDLE);
this.cpu.writeHooks[config.TWCR] = (value) => {
this.cpu.data[config.TWCR] = value;
const clearInt = value & TWCR_TWINT;
this.cpu.clearInterruptByFlag(this.TWI, value);
this.cpu.updateInterruptEnable(this.TWI, value);
const { status } = this;
if (clearInt && value & TWCR_TWEN && !this.busy) {
const twdrValue = this.cpu.data[this.config.TWDR];
this.cpu.addClockEvent(() => {
if (value & TWCR_TWSTA) {
this.busy = true;
this.eventHandler.start(status !== STATUS_TWI_IDLE);
}
else if (value & TWCR_TWSTO) {
this.busy = true;
this.eventHandler.stop();
}
else if (status === STATUS_START || status === STATUS_REPEATED_START) {
this.busy = true;
this.eventHandler.connectToSlave(twdrValue >> 1, twdrValue & 0x1 ? false : true);
}
else if (status === STATUS_SLAW_ACK || status === STATUS_DATA_SENT_ACK) {
this.busy = true;
this.eventHandler.writeByte(twdrValue);
}
else if (status === STATUS_SLAR_ACK || status === STATUS_DATA_RECEIVED_ACK) {
this.busy = true;
const ack = !!(value & TWCR_TWEA);
this.eventHandler.readByte(ack);
}
}, 0);
return true;
}
};
}
get prescaler() {
switch (this.cpu.data[this.config.TWSR] & TWSR_TWPS_MASK) {
case 0:
return 1;
case 1:
return 4;
case 2:
return 16;
case 3:
return 64;
}
// We should never get here:
throw new Error('Invalid prescaler value!');
}
get sclFrequency() {
return this.freqHz / (16 + 2 * this.cpu.data[this.config.TWBR] * this.prescaler);
}
completeStart() {
this.busy = false;
this.updateStatus(this.status === STATUS_TWI_IDLE ? STATUS_START : STATUS_REPEATED_START);
}
completeStop() {
this.busy = false;
this.cpu.data[this.config.TWCR] &= ~TWCR_TWSTO;
this.updateStatus(STATUS_TWI_IDLE);
}
completeConnect(ack) {
this.busy = false;
if (this.cpu.data[this.config.TWDR] & 0x1) {
this.updateStatus(ack ? STATUS_SLAR_ACK : STATUS_SLAR_NACK);
}
else {
this.updateStatus(ack ? STATUS_SLAW_ACK : STATUS_SLAW_NACK);
}
}
completeWrite(ack) {
this.busy = false;
this.updateStatus(ack ? STATUS_DATA_SENT_ACK : STATUS_DATA_SENT_NACK);
}
completeRead(value) {
this.busy = false;
const ack = !!(this.cpu.data[this.config.TWCR] & TWCR_TWEA);
this.cpu.data[this.config.TWDR] = value;
this.updateStatus(ack ? STATUS_DATA_RECEIVED_ACK : STATUS_DATA_RECEIVED_NACK);
}
get status() {
return this.cpu.data[this.config.TWSR] & TWSR_TWS_MASK;
}
updateStatus(value) {
const { TWSR } = this.config;
this.cpu.data[TWSR] = (this.cpu.data[TWSR] & ~TWSR_TWS_MASK) | value;
this.cpu.setInterruptFlag(this.TWI);
}
}
+238
View File
@@ -0,0 +1,238 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
export const usart0Config = {
rxCompleteInterrupt: 0x24,
dataRegisterEmptyInterrupt: 0x26,
txCompleteInterrupt: 0x28,
UCSRA: 0xc0,
UCSRB: 0xc1,
UCSRC: 0xc2,
UBRRL: 0xc4,
UBRRH: 0xc5,
UDR: 0xc6,
};
/* eslint-disable @typescript-eslint/no-unused-vars */
// Register bits:
const UCSRA_RXC = 0x80; // USART Receive Complete
const UCSRA_TXC = 0x40; // USART Transmit Complete
const UCSRA_UDRE = 0x20; // USART Data Register Empty
const UCSRA_FE = 0x10; // Frame Error
const UCSRA_DOR = 0x8; // Data OverRun
const UCSRA_UPE = 0x4; // USART Parity Error
const UCSRA_U2X = 0x2; // Double the USART Transmission Speed
const UCSRA_MPCM = 0x1; // Multi-processor Communication Mode
const UCSRA_CFG_MASK = UCSRA_U2X;
const UCSRB_RXCIE = 0x80; // RX Complete Interrupt Enable
const UCSRB_TXCIE = 0x40; // TX Complete Interrupt Enable
const UCSRB_UDRIE = 0x20; // USART Data Register Empty Interrupt Enable
const UCSRB_RXEN = 0x10; // Receiver Enable
const UCSRB_TXEN = 0x8; // Transmitter Enable
const UCSRB_UCSZ2 = 0x4; // Character Size 2
const UCSRB_RXB8 = 0x2; // Receive Data Bit 8
const UCSRB_TXB8 = 0x1; // Transmit Data Bit 8
const UCSRB_CFG_MASK = UCSRB_UCSZ2 | UCSRB_RXEN | UCSRB_TXEN;
const UCSRC_UMSEL1 = 0x80; // USART Mode Select 1
const UCSRC_UMSEL0 = 0x40; // USART Mode Select 0
const UCSRC_UPM1 = 0x20; // Parity Mode 1
const UCSRC_UPM0 = 0x10; // Parity Mode 0
const UCSRC_USBS = 0x8; // Stop Bit Select
const UCSRC_UCSZ1 = 0x4; // Character Size 1
const UCSRC_UCSZ0 = 0x2; // Character Size 0
const UCSRC_UCPOL = 0x1; // Clock Polarity
/* eslint-enable @typescript-eslint/no-unused-vars */
const rxMasks = {
5: 0x1f,
6: 0x3f,
7: 0x7f,
8: 0xff,
9: 0xff,
};
export class AVRUSART {
constructor(cpu, config, freqHz) {
this.cpu = cpu;
this.config = config;
this.freqHz = freqHz;
this.onByteTransmit = null;
this.onLineTransmit = null;
this.onRxComplete = null;
this.onConfigurationChange = null;
this.rxBusyValue = false;
this.rxByte = 0;
this.lineBuffer = '';
// Interrupts
this.RXC = {
address: this.config.rxCompleteInterrupt,
flagRegister: this.config.UCSRA,
flagMask: UCSRA_RXC,
enableRegister: this.config.UCSRB,
enableMask: UCSRB_RXCIE,
constant: true,
};
this.UDRE = {
address: this.config.dataRegisterEmptyInterrupt,
flagRegister: this.config.UCSRA,
flagMask: UCSRA_UDRE,
enableRegister: this.config.UCSRB,
enableMask: UCSRB_UDRIE,
};
this.TXC = {
address: this.config.txCompleteInterrupt,
flagRegister: this.config.UCSRA,
flagMask: UCSRA_TXC,
enableRegister: this.config.UCSRB,
enableMask: UCSRB_TXCIE,
};
this.reset();
this.cpu.writeHooks[config.UCSRA] = (value, oldValue) => {
var _a;
cpu.data[config.UCSRA] = value & (UCSRA_MPCM | UCSRA_U2X);
cpu.clearInterruptByFlag(this.TXC, value);
if ((value & UCSRA_CFG_MASK) !== (oldValue & UCSRA_CFG_MASK)) {
(_a = this.onConfigurationChange) === null || _a === void 0 ? void 0 : _a.call(this);
}
return true;
};
this.cpu.writeHooks[config.UCSRB] = (value, oldValue) => {
var _a;
cpu.updateInterruptEnable(this.RXC, value);
cpu.updateInterruptEnable(this.UDRE, value);
cpu.updateInterruptEnable(this.TXC, value);
if (value & UCSRB_RXEN && oldValue & UCSRB_RXEN) {
cpu.clearInterrupt(this.RXC);
}
if (value & UCSRB_TXEN && !(oldValue & UCSRB_TXEN)) {
// Enabling the transmission - mark UDR as empty
cpu.setInterruptFlag(this.UDRE);
}
cpu.data[config.UCSRB] = value;
if ((value & UCSRB_CFG_MASK) !== (oldValue & UCSRB_CFG_MASK)) {
(_a = this.onConfigurationChange) === null || _a === void 0 ? void 0 : _a.call(this);
}
return true;
};
this.cpu.writeHooks[config.UCSRC] = (value) => {
var _a;
cpu.data[config.UCSRC] = value;
(_a = this.onConfigurationChange) === null || _a === void 0 ? void 0 : _a.call(this);
return true;
};
this.cpu.readHooks[config.UDR] = () => {
var _a;
const mask = (_a = rxMasks[this.bitsPerChar]) !== null && _a !== void 0 ? _a : 0xff;
const result = this.rxByte & mask;
this.rxByte = 0;
this.cpu.clearInterrupt(this.RXC);
return result;
};
this.cpu.writeHooks[config.UDR] = (value) => {
if (this.onByteTransmit) {
this.onByteTransmit(value);
}
if (this.onLineTransmit) {
const ch = String.fromCharCode(value);
if (ch === '\n') {
this.onLineTransmit(this.lineBuffer);
this.lineBuffer = '';
}
else {
this.lineBuffer += ch;
}
}
this.cpu.addClockEvent(() => {
cpu.setInterruptFlag(this.UDRE);
cpu.setInterruptFlag(this.TXC);
}, this.cyclesPerChar);
this.cpu.clearInterrupt(this.TXC);
this.cpu.clearInterrupt(this.UDRE);
};
this.cpu.writeHooks[config.UBRRH] = (value) => {
var _a;
this.cpu.data[config.UBRRH] = value;
(_a = this.onConfigurationChange) === null || _a === void 0 ? void 0 : _a.call(this);
return true;
};
this.cpu.writeHooks[config.UBRRL] = (value) => {
var _a;
this.cpu.data[config.UBRRL] = value;
(_a = this.onConfigurationChange) === null || _a === void 0 ? void 0 : _a.call(this);
return true;
};
}
reset() {
this.cpu.data[this.config.UCSRA] = UCSRA_UDRE;
this.cpu.data[this.config.UCSRB] = 0;
this.cpu.data[this.config.UCSRC] = UCSRC_UCSZ1 | UCSRC_UCSZ0; // default: 8 bits per byte
this.rxBusyValue = false;
this.rxByte = 0;
this.lineBuffer = '';
}
get rxBusy() {
return this.rxBusyValue;
}
writeByte(value, immediate = false) {
var _a;
const { cpu } = this;
if (this.rxBusyValue || !this.rxEnable) {
return false;
}
if (immediate) {
this.rxByte = value;
cpu.setInterruptFlag(this.RXC);
(_a = this.onRxComplete) === null || _a === void 0 ? void 0 : _a.call(this);
}
else {
this.rxBusyValue = true;
cpu.addClockEvent(() => {
this.rxBusyValue = false;
this.writeByte(value, true);
}, this.cyclesPerChar);
return true;
}
}
get cyclesPerChar() {
const symbolsPerChar = 1 + this.bitsPerChar + this.stopBits + (this.parityEnabled ? 1 : 0);
return (this.UBRR + 1) * this.multiplier * symbolsPerChar;
}
get UBRR() {
const { UBRRH, UBRRL } = this.config;
return (this.cpu.data[UBRRH] << 8) | this.cpu.data[UBRRL];
}
get multiplier() {
return this.cpu.data[this.config.UCSRA] & UCSRA_U2X ? 8 : 16;
}
get rxEnable() {
return !!(this.cpu.data[this.config.UCSRB] & UCSRB_RXEN);
}
get txEnable() {
return !!(this.cpu.data[this.config.UCSRB] & UCSRB_TXEN);
}
get baudRate() {
return Math.floor(this.freqHz / (this.multiplier * (1 + this.UBRR)));
}
get bitsPerChar() {
const ucsz = ((this.cpu.data[this.config.UCSRC] & (UCSRC_UCSZ1 | UCSRC_UCSZ0)) >> 1) |
(this.cpu.data[this.config.UCSRB] & UCSRB_UCSZ2);
switch (ucsz) {
case 0:
return 5;
case 1:
return 6;
case 2:
return 7;
case 3:
return 8;
default: // 4..6 are reserved
case 7:
return 9;
}
}
get stopBits() {
return this.cpu.data[this.config.UCSRC] & UCSRC_USBS ? 2 : 1;
}
get parityEnabled() {
return this.cpu.data[this.config.UCSRC] & UCSRC_UPM1 ? true : false;
}
get parityOdd() {
return this.cpu.data[this.config.UCSRC] & UCSRC_UPM0 ? true : false;
}
}
+118
View File
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
const USICR = 0x2d;
const USISR = 0x2e;
const USIDR = 0x2f;
const USIBR = 0x30;
// USISR bits
const USICNT_MASK = 0xf;
const USIDC = 1 << 4;
const USIPF = 1 << 5;
const USIOIF = 1 << 6;
const USISIF = 1 << 7;
// USICR bits
const USITC = 1 << 0;
const USICLK = 1 << 1;
const USICS0 = 1 << 2;
const USICS1 = 1 << 3;
const USIWM0 = 1 << 4;
const USIWM1 = 1 << 5;
const USIOIE = 1 << 6;
const USISIE = 1 << 7;
export class AVRUSI {
constructor(cpu, port, portPin, dataPin, clockPin) {
// Interrupts
this.START = {
address: 0xd,
flagRegister: USISR,
flagMask: USISIF,
enableRegister: USICR,
enableMask: USISIE,
};
this.OVF = {
address: 0xe,
flagRegister: USISR,
flagMask: USIOIF,
enableRegister: USICR,
enableMask: USIOIE,
};
const PIN = portPin;
const PORT = PIN + 2;
port.addListener((value) => {
const twoWire = (cpu.data[USICR] & USIWM1) === USIWM1;
if (twoWire) {
if (value & (1 << clockPin) && !(value & (1 << dataPin))) {
// Start condition detected
cpu.setInterruptFlag(this.START);
}
if (value & (1 << clockPin) && value & (1 << dataPin)) {
// Stop condition detected
cpu.data[USISR] |= USIPF;
}
}
});
const updateOutput = () => {
const oldValue = cpu.data[PORT];
const newValue = cpu.data[USIDR] & 0x80 ? oldValue | (1 << dataPin) : oldValue & ~(1 << dataPin);
cpu.writeHooks[PORT](newValue, oldValue, PORT, 0xff);
if (newValue & 0x80 && !(cpu.data[PIN] & 0x80)) {
cpu.data[USISR] |= USIDC; // Shout output HIGH (pulled-up), but input is LOW
}
else {
cpu.data[USISR] &= ~USIDC;
}
};
const count = () => {
const counter = (cpu.data[USISR] + 1) & USICNT_MASK;
cpu.data[USISR] = (cpu.data[USISR] & ~USICNT_MASK) | counter;
if (!counter) {
cpu.data[USIBR] = cpu.data[USIDR];
cpu.setInterruptFlag(this.OVF);
}
};
const shift = (inputValue) => {
cpu.data[USIDR] = (cpu.data[USIDR] << 1) | inputValue;
updateOutput();
};
cpu.writeHooks[USIDR] = (value) => {
cpu.data[USIDR] = value;
updateOutput();
return true;
};
cpu.writeHooks[USISR] = (value) => {
const writeClearMask = USISIF | USIOIF | USIPF;
cpu.data[USISR] = (cpu.data[USISR] & writeClearMask & ~value) | (value & 0xf);
cpu.clearInterruptByFlag(this.START, value);
cpu.clearInterruptByFlag(this.OVF, value);
return true;
};
cpu.writeHooks[USICR] = (value) => {
cpu.data[USICR] = value & ~(USICLK | USITC);
cpu.updateInterruptEnable(this.START, value);
cpu.updateInterruptEnable(this.OVF, value);
const clockSrc = value & ((USICS1 | USICS0) >> 2);
const mode = value & ((USIWM1 | USIWM0) >> 4);
const usiClk = value & USICLK;
port.openCollector = mode >= 2 ? 1 << dataPin : 0;
const inputValue = cpu.data[PIN] & (1 << dataPin) ? 1 : 0;
if (usiClk && !clockSrc) {
shift(inputValue);
count();
}
if (value & USITC) {
cpu.writeHooks[PIN](1 << clockPin, cpu.data[PIN], PIN, 0xff);
const newValue = cpu.data[PIN] & (1 << clockPin);
if (usiClk && (clockSrc === 2 || clockSrc === 3)) {
if (clockSrc === 2 && newValue) {
shift(inputValue);
}
if (clockSrc === 3 && !newValue) {
shift(inputValue);
}
count();
}
return true;
}
};
}
}
+110
View File
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
// Register bits:
const MCUSR_WDRF = 0x8; // Watchdog System Reset Flag
const WDTCSR_WDIF = 0x80;
const WDTCSR_WDIE = 0x40;
const WDTCSR_WDP3 = 0x20;
const WDTCSR_WDCE = 0x10; // Watchdog Change Enable
const WDTCSR_WDE = 0x8;
const WDTCSR_WDP2 = 0x4;
const WDTCSR_WDP1 = 0x2;
const WDTCSR_WDP0 = 0x1;
const WDTCSR_WDP210 = WDTCSR_WDP2 | WDTCSR_WDP1 | WDTCSR_WDP0;
const WDTCSR_PROTECT_MASK = WDTCSR_WDE | WDTCSR_WDP3 | WDTCSR_WDP210;
export const watchdogConfig = {
watchdogInterrupt: 0x0c,
MCUSR: 0x54,
WDTCSR: 0x60,
};
export class AVRWatchdog {
constructor(cpu, config, clock) {
this.cpu = cpu;
this.config = config;
this.clock = clock;
this.clockFrequency = 128000;
/**
* Used to keep track on the last write to WDCE. Once written, the WDE/WDP* bits can be changed.
*/
this.changeEnabledCycles = 0;
this.watchdogTimeout = 0;
this.enabledValue = false;
this.scheduled = false;
// Interrupts
this.Watchdog = {
address: this.config.watchdogInterrupt,
flagRegister: this.config.WDTCSR,
flagMask: WDTCSR_WDIF,
enableRegister: this.config.WDTCSR,
enableMask: WDTCSR_WDIE,
};
this.checkWatchdog = () => {
if (this.enabled && this.cpu.cycles >= this.watchdogTimeout) {
// Watchdog timed out!
const wdtcsr = this.cpu.data[this.config.WDTCSR];
if (wdtcsr & WDTCSR_WDIE) {
this.cpu.setInterruptFlag(this.Watchdog);
}
if (wdtcsr & WDTCSR_WDE) {
if (wdtcsr & WDTCSR_WDIE) {
this.cpu.data[this.config.WDTCSR] &= ~WDTCSR_WDIE;
}
else {
this.cpu.reset();
this.scheduled = false;
this.cpu.data[this.config.MCUSR] |= MCUSR_WDRF;
return;
}
}
this.resetWatchdog();
}
if (this.enabled) {
this.scheduled = true;
this.cpu.addClockEvent(this.checkWatchdog, this.watchdogTimeout - this.cpu.cycles);
}
else {
this.scheduled = false;
}
};
const { WDTCSR } = config;
this.cpu.onWatchdogReset = () => {
this.resetWatchdog();
};
cpu.writeHooks[WDTCSR] = (value, oldValue) => {
if (value & WDTCSR_WDCE && value & WDTCSR_WDE) {
this.changeEnabledCycles = this.cpu.cycles + 4;
value = value & ~WDTCSR_PROTECT_MASK;
}
else {
if (this.cpu.cycles >= this.changeEnabledCycles) {
value = (value & ~WDTCSR_PROTECT_MASK) | (oldValue & WDTCSR_PROTECT_MASK);
}
this.enabledValue = !!(value & WDTCSR_WDE || value & WDTCSR_WDIE);
this.cpu.data[WDTCSR] = value;
}
if (this.enabled) {
this.resetWatchdog();
}
if (this.enabled && !this.scheduled) {
this.cpu.addClockEvent(this.checkWatchdog, this.watchdogTimeout - this.cpu.cycles);
}
this.cpu.clearInterruptByFlag(this.Watchdog, value);
return true;
};
}
resetWatchdog() {
const cycles = Math.floor((this.clock.frequency / this.clockFrequency) * this.prescaler);
this.watchdogTimeout = this.cpu.cycles + cycles;
}
get enabled() {
return this.enabledValue;
}
/**
* The base clock frequency is 128KHz. Thus, a prescaler of 2048 gives 16ms timeout.
*/
get prescaler() {
const wdtcsr = this.cpu.data[this.config.WDTCSR];
const value = ((wdtcsr & WDTCSR_WDP3) >> 2) | (wdtcsr & WDTCSR_WDP210);
return 2048 << value;
}
}
+3
View File
@@ -0,0 +1,3 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
export {};
+915
View File
@@ -0,0 +1,915 @@
/**
* Assemble AVR programs into a list of bytes.
* Based on code from https://github.com/tadpol/Avrian-Jump,
* refactored to TypeScript by Uri Shaked.
*
* This was written with http://www.atmel.com/atmel/acrobat/doc0856.pdf
* It is a bit short of features often found in an assembler, but there is enough here to build
* simple programs and run them.
*
* It would be nice someday to add device support, just to give errors on unsupported
* instructions. Macros would be nice too.
*
* Copyright (C) 2020, Uri Shaked
* Copyright (c) 2012 Michael Conrad Tadpol Tilstra
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Get a destination register index from a string and shift it to where it
* is most commonly found.
* Also, make sure it is within the valid range.
*/
function destRindex(r, min = 0, max = 31) {
const match = r.match(/[Rr](\d{1,2})/);
if (!match) {
throw 'Not a register: ' + r;
}
const d = parseInt(match[1]);
if (d < min || d > max) {
throw 'Rd out of range: ' + min + '<>' + max;
}
return (d & 0x1f) << 4;
}
/**
* Get a source register index from a string and shift it to where it is
* most commonly found.
* Also, make sure it is within the valid range.
*/
function srcRindex(r, min = 0, max = 31) {
const match = r.match(/[Rr](\d{1,2})/);
if (!match) {
throw 'Not a register: ' + r;
}
const d = parseInt(match[1]);
if (d < min || d > max) {
throw 'Rd out of range: ' + min + '<>' + max;
}
let s = d & 0xf;
s |= ((d >> 4) & 1) << 9;
return s;
}
/**
* Get a constant value and check that it is in range.
*/
function constValue(r, min = 0, max = 255) {
const d = typeof r === 'string' ? parseInt(r) : r;
if (isNaN(d)) {
throw 'constant is not a number.';
}
if (d < min || d > max) {
throw '[Ks] out of range: ' + min + '<>' + max;
}
return d;
}
/*
* Fit a twos-complement number into the specific bit count
*/
function fitTwoC(r, bits) {
if (bits < 2) {
throw 'Need at least 2 bits to be signed.';
}
if (bits > 16) {
throw 'fitTwoC only works on 16bit numbers for now.';
}
if (Math.abs(r) > Math.pow(2, bits - 1))
throw 'Not enough bits for number. (' + r + ', ' + bits + ')';
if (r < 0) {
r = 0xffff + r + 1;
}
const mask = 0xffff >> (16 - bits);
return r & mask;
}
/**
* Determin if input is an address or label and lookup if required.
* If label that doesn't exist, return NaN.
* If offset is not 0, convert from absolute address to relative.
*/
function constOrLabel(c, labels, offset = 0) {
if (typeof c === 'string') {
let d = parseInt(c);
if (isNaN(d)) {
if (c in labels) {
d = labels[c] - offset;
}
else {
return NaN;
}
}
c = d;
}
return c;
}
/**
* Convert number to hex and left pad it
* @param len default to words.
*/
function zeroPad(r, len = 4) {
r = Number(r).toString(16);
const base = Array(len + 1).join('0');
const t = base.substr(0, len - r.length) + r;
return t;
}
/**
* Get an Indirect Address Register and shift it to where it is commonly found.
*/
function stldXYZ(xyz) {
switch (xyz) {
case 'X':
return 0x900c;
case 'X+':
return 0x900d;
case '-X':
return 0x900e;
case 'Y':
return 0x8008;
case 'Y+':
return 0x9009;
case '-Y':
return 0x900a;
case 'Z':
return 0x8000;
case 'Z+':
return 0x9001;
case '-Z':
return 0x9002;
default:
throw 'Not -?[XYZ]\\+?';
}
}
/**
* Get an Indirect Address Register with displacement and shift it to where it is commonly found.
*/
function stldYZq(yzq) {
const d = yzq.match(/([YZ])\+(\d+)/);
let r = 0x8000;
if (d == null) {
throw 'Invalid arguments';
}
switch (d[1]) {
case 'Y':
r |= 0x8;
break;
case 'Z':
/* r|= 0; */
break;
default:
throw 'Not Y or Z with q';
}
const q = parseInt(d[2]);
if (q < 0 || q > 64) {
throw 'q is out of range';
}
r |= ((q & 0x20) << 8) | ((q & 0x18) << 7) | (q & 0x7);
return r;
}
const SEflag = (a) => zeroPad(0x9408 | (constValue(a, 0, 7) << 4));
/**
* Table of instructions that can be assembled.
*/
const OPTABLE = {
ADD(a, b) {
const r = 0x0c00 | destRindex(a) | srcRindex(b);
return zeroPad(r);
},
ADC(a, b) {
const r = 0x1c00 | destRindex(a) | srcRindex(b);
return zeroPad(r);
},
ADIW(a, b) {
let r = 0x9600;
const dm = a.match(/[Rr](24|26|28|30)/);
if (!dm) {
throw 'Rd must be 24, 26, 28, or 30';
}
let d = parseInt(dm[1]);
d = (d - 24) / 2;
r |= (d & 0x3) << 4;
const k = constValue(b, 0, 63);
r |= ((k & 0x30) << 2) | (k & 0x0f);
return zeroPad(r);
},
AND(a, b) {
const r = 0x2000 | destRindex(a) | srcRindex(b);
return zeroPad(r);
},
ANDI(a, b) {
let r = 0x7000 | (destRindex(a, 16, 31) & 0xf0);
const k = constValue(b);
r |= ((k & 0xf0) << 4) | (k & 0xf);
return zeroPad(r);
},
ASR(a) {
const r = 0x9405 | destRindex(a);
return zeroPad(r);
},
BCLR(a) {
let r = 0x9488;
const s = constValue(a, 0, 7);
r |= (s & 0x7) << 4;
return zeroPad(r);
},
BLD(a, b) {
const r = 0xf800 | destRindex(a) | (constValue(b, 0, 7) & 0x7);
return zeroPad(r);
},
BRBC(a, b, byteLoc, labels) {
const k = constOrLabel(b, labels, byteLoc + 2);
if (isNaN(k)) {
return (l) => OPTABLE['BRBC'](a, b, byteLoc, l);
}
let r = 0xf400 | constValue(a, 0, 7);
r |= fitTwoC(constValue(k >> 1, -64, 63), 7) << 3;
return zeroPad(r);
},
BRBS(a, b, byteLoc, labels) {
const k = constOrLabel(b, labels, byteLoc + 2);
if (isNaN(k)) {
return (l) => OPTABLE['BRBS'](a, b, byteLoc, l);
}
let r = 0xf000 | constValue(a, 0, 7);
r |= fitTwoC(constValue(k >> 1, -64, 63), 7) << 3;
return zeroPad(r);
},
BRCC(a, _, byteLoc, labels) {
return OPTABLE['BRBC']('0', a, byteLoc, labels);
},
BRCS(a, _, byteLoc, labels) {
return OPTABLE['BRBS']('0', a, byteLoc, labels);
},
BREAK() {
return '9598';
},
BREQ(a, _, byteLoc, labels) {
return OPTABLE['BRBS']('1', a, byteLoc, labels);
},
BRGE(a, _, byteLoc, labels) {
return OPTABLE['BRBC']('4', a, byteLoc, labels);
},
BRHC(a, _, byteLoc, labels) {
return OPTABLE['BRBC']('5', a, byteLoc, labels);
},
BRHS(a, _, byteLoc, labels) {
return OPTABLE['BRBS']('5', a, byteLoc, labels);
},
BRID(a, _, byteLoc, labels) {
return OPTABLE['BRBC']('7', a, byteLoc, labels);
},
BRIE(a, _, byteLoc, labels) {
return OPTABLE['BRBS']('7', a, byteLoc, labels);
},
BRLO(a, _, byteLoc, labels) {
return OPTABLE['BRBS']('0', a, byteLoc, labels);
},
BRLT(a, _, byteLoc, labels) {
return OPTABLE['BRBS']('4', a, byteLoc, labels);
},
BRMI(a, _, byteLoc, labels) {
return OPTABLE['BRBS']('2', a, byteLoc, labels);
},
BRNE(a, _, byteLoc, labels) {
return OPTABLE['BRBC']('1', a, byteLoc, labels);
},
BRPL(a, _, byteLoc, labels) {
return OPTABLE['BRBC']('2', a, byteLoc, labels);
},
BRSH(a, _, byteLoc, labels) {
return OPTABLE['BRBC']('0', a, byteLoc, labels);
},
BRTC(a, _, byteLoc, labels) {
return OPTABLE['BRBC']('6', a, byteLoc, labels);
},
BRTS(a, _, byteLoc, labels) {
return OPTABLE['BRBS']('6', a, byteLoc, labels);
},
BRVC(a, _, byteLoc, labels) {
return OPTABLE['BRBC']('3', a, byteLoc, labels);
},
BRVS(a, _, byteLoc, labels) {
return OPTABLE['BRBS']('3', a, byteLoc, labels);
},
BSET(a) {
let r = 0x9408;
const s = constValue(a, 0, 7);
r |= (s & 0x7) << 4;
return zeroPad(r);
},
BST(a, b) {
const r = 0xfa00 | destRindex(a) | constValue(b, 0, 7);
return zeroPad(r);
},
CALL(a, b, byteLoc, labels) {
let k = constOrLabel(a, labels);
if (isNaN(k)) {
return [(l) => OPTABLE['CALL'](a, b, byteLoc, l), 'xxxx'];
}
let r = 0x940e;
k = constValue(k, 0, 0x400000) >> 1;
const lk = k & 0xffff;
const hk = (k >> 16) & 0x3f;
r |= ((hk & 0x3e) << 3) | (hk & 1);
return [zeroPad(r), zeroPad(lk)];
},
CBI(a, b) {
const r = 0x9800 | (constValue(a, 0, 31) << 3) | constValue(b, 0, 7);
return zeroPad(r);
},
CRB(a, b, byteLoc, l) {
const k = constValue(b);
return OPTABLE['ANDI'](a.toString(), (~k & 0xff).toString(), byteLoc, l);
},
CLC() {
return '9488';
},
CLH() {
return '94d8';
},
CLI() {
return '94f8';
},
CLN() {
return '94a8';
},
CLR(a, _, byteLoc, l) {
return OPTABLE['EOR'](a, a, byteLoc, l);
},
CLS() {
return '94c8';
},
CLT() {
return '94e8';
},
CLV() {
return '94b8';
},
CLZ() {
return '9498';
},
COM(a) {
const r = 0x9400 | destRindex(a);
return zeroPad(r);
},
CP(a, b) {
const r = 0x1400 | destRindex(a) | srcRindex(b);
return zeroPad(r);
},
CPC(a, b) {
const r = 0x0400 | destRindex(a) | srcRindex(b);
return zeroPad(r);
},
CPI(a, b) {
let r = 0x3000 | (destRindex(a, 16, 31) & 0xf0);
const k = constValue(b);
r |= ((k & 0xf0) << 4) | (k & 0xf);
return zeroPad(r);
},
CPSE(a, b) {
const r = 0x1000 | destRindex(a) | srcRindex(b);
return zeroPad(r);
},
DEC(a) {
const r = 0x940a | destRindex(a);
return zeroPad(r);
},
DES(a) {
const r = 0x940b | (constValue(a, 0, 15) << 4);
return zeroPad(r);
},
EICALL() {
return '9519';
},
EIJMP() {
return '9419';
},
ELPM(a, b) {
if (typeof a === 'undefined' || a === '') {
return '95d8';
}
else {
let r = 0x9000 | destRindex(a);
switch (b) {
case 'Z':
r |= 6;
break;
case 'Z+':
r |= 7;
break;
default:
throw 'Bad operand';
}
return zeroPad(r);
}
},
EOR(a, b) {
const r = 0x2400 | destRindex(a) | srcRindex(b);
return zeroPad(r);
},
FMUL(a, b) {
const r = 0x0308 | (destRindex(a, 16, 23) & 0x70) | (srcRindex(b, 16, 23) & 0x7);
return zeroPad(r);
},
FMULS(a, b) {
const r = 0x0380 | (destRindex(a, 16, 23) & 0x70) | (srcRindex(b, 16, 23) & 0x7);
return zeroPad(r);
},
FMULSU(a, b) {
const r = 0x0388 | (destRindex(a, 16, 23) & 0x70) | (srcRindex(b, 16, 23) & 0x7);
return zeroPad(r);
},
ICALL() {
return '9509';
},
IJMP() {
return '9409';
},
IN(a, b) {
let r = 0xb000 | destRindex(a);
const A = constValue(b, 0, 63);
r |= ((A & 0x30) << 5) | (A & 0x0f);
return zeroPad(r);
},
INC(a) {
const r = 0x9403 | destRindex(a);
return zeroPad(r);
},
JMP(a, b, byteLoc, labels) {
let k = constOrLabel(a, labels);
if (isNaN(k)) {
return [(l) => OPTABLE['JMP'](a, b, byteLoc, l), 'xxxx'];
}
let r = 0x940c;
k = constValue(k, 0, 0x400000) >> 1;
const lk = k & 0xffff;
const hk = (k >> 16) & 0x3f;
r |= ((hk & 0x3e) << 3) | (hk & 1);
return [zeroPad(r), zeroPad(lk)];
},
LAC(a, b) {
if (a !== 'Z') {
throw 'First Operand is not Z';
}
const r = 0x9206 | destRindex(b);
return zeroPad(r);
},
LAS(a, b) {
if (a !== 'Z') {
throw 'First Operand is not Z';
}
const r = 0x9205 | destRindex(b);
return zeroPad(r);
},
LAT(a, b) {
if (a !== 'Z') {
throw 'First Operand is not Z';
}
const r = 0x9207 | destRindex(b);
return zeroPad(r);
},
LD(a, b) {
const r = 0x0000 | destRindex(a) | stldXYZ(b);
return zeroPad(r);
},
LDD(a, b) {
const r = 0x0000 | destRindex(a) | stldYZq(b);
return zeroPad(r);
},
LDI(a, b) {
let r = 0xe000 | (destRindex(a, 16, 31) & 0xf0);
const k = constValue(b);
r |= ((k & 0xf0) << 4) | (k & 0xf);
return zeroPad(r);
},
LDS(a, b) {
const k = constValue(b, 0, 65535);
const r = 0x9000 | destRindex(a);
return [zeroPad(r), zeroPad(k)];
},
LPM(a, b) {
if (typeof a === 'undefined' || a === '') {
return '95c8';
}
else {
let r = 0x9000 | destRindex(a);
switch (b) {
case 'Z':
r |= 4;
break;
case 'Z+':
r |= 5;
break;
default:
throw 'Bad operand';
}
return zeroPad(r);
}
},
LSL(a, _, byteLoc, l) {
return OPTABLE['ADD'](a, a, byteLoc, l);
},
LSR(a) {
const r = 0x9406 | destRindex(a);
return zeroPad(r);
},
MOV(a, b) {
const r = 0x2c00 | destRindex(a) | srcRindex(b);
return zeroPad(r);
},
MOVW(a, b) {
/* use destRindex on both here for simpler shifting */
const r = 0x0100 | ((destRindex(a) >> 1) & 0xf0) | ((destRindex(b) >> 5) & 0xf);
return zeroPad(r);
},
MUL(a, b) {
const r = 0x9c00 | destRindex(a) | srcRindex(b);
return zeroPad(r);
},
MULS(a, b) {
const r = 0x0200 | (destRindex(a, 16, 31) & 0xf0) | (srcRindex(b, 16, 31) & 0xf);
return zeroPad(r);
},
MULSU(a, b) {
const r = 0x0300 | (destRindex(a, 16, 23) & 0x70) | (srcRindex(b, 16, 23) & 0x7);
return zeroPad(r);
},
NEG(a) {
const r = 0x9401 | destRindex(a);
return zeroPad(r);
},
NOP() {
return '0000';
},
OR(a, b) {
const r = 0x2800 | destRindex(a) | srcRindex(b);
return zeroPad(r);
},
ORI(a, b) {
let r = 0x6000 | (destRindex(a, 16, 31) & 0xf0);
const k = constValue(b);
r |= ((k & 0xf0) << 4) | (k & 0xf);
return zeroPad(r);
},
OUT(a, b) {
let r = 0xb800 | destRindex(b);
const A = constValue(a, 0, 63);
r |= ((A & 0x30) << 5) | (A & 0x0f);
return zeroPad(r);
},
POP(a) {
const r = 0x900f | destRindex(a);
return zeroPad(r);
},
PUSH(a) {
const r = 0x920f | destRindex(a);
return zeroPad(r);
},
RCALL(a, b, byteLoc, labels) {
const k = constOrLabel(a, labels, byteLoc + 2);
if (isNaN(k)) {
return (l) => OPTABLE['RCALL'](a, b, byteLoc, l);
}
const r = 0xd000 | fitTwoC(constValue(k >> 1, -2048, 2047), 12);
return zeroPad(r);
},
RET() {
return '9508';
},
RETI() {
return '9518';
},
RJMP(a, b, byteLoc, labels) {
const k = constOrLabel(a, labels, byteLoc + 2);
if (isNaN(k)) {
return (l) => OPTABLE['RJMP'](a, b, byteLoc, l);
}
const r = 0xc000 | fitTwoC(constValue(k >> 1, -2048, 2047), 12);
return zeroPad(r);
},
ROL(a, _, byteLoc, l) {
return OPTABLE['ADC'](a, a, byteLoc, l);
},
ROR(a) {
const r = 0x9407 | destRindex(a);
return zeroPad(r);
},
SBC(a, b) {
const r = 0x0800 | destRindex(a) | srcRindex(b);
return zeroPad(r);
},
SBCI(a, b) {
let r = 0x4000 | (destRindex(a, 16, 31) & 0xf0);
const k = constValue(b);
r |= ((k & 0xf0) << 4) | (k & 0x0f);
return zeroPad(r);
},
SBI(a, b) {
const r = 0x9a00 | (constValue(a, 0, 31) << 3) | constValue(b, 0, 7);
return zeroPad(r);
},
SBIC(a, b) {
const r = 0x9900 | (constValue(a, 0, 31) << 3) | constValue(b, 0, 7);
return zeroPad(r);
},
SBIS(a, b) {
const r = 0x9b00 | (constValue(a, 0, 31) << 3) | constValue(b, 0, 7);
return zeroPad(r);
},
SBIW(a, b) {
let r = 0x9700;
const dm = a.match(/[Rr](24|26|28|30)/);
if (!dm) {
throw 'Rd must be 24, 26, 28, or 30';
}
let d = parseInt(dm[1]);
d = (d - 24) / 2;
r |= (d & 0x3) << 4;
const k = constValue(b, 0, 63);
r |= ((k & 0x30) << 2) | (k & 0x0f);
return zeroPad(r);
},
SBR(a, b) {
let r = 0x6000 | (destRindex(a, 16, 31) & 0xf0);
const k = constValue(b);
r |= ((k & 0xf0) << 4) | (k & 0x0f);
return zeroPad(r);
},
SBRC(a, b) {
const r = 0xfc00 | destRindex(a) | constValue(b, 0, 7);
return zeroPad(r);
},
SBRS(a, b) {
const r = 0xfe00 | destRindex(a) | constValue(b, 0, 7);
return zeroPad(r);
},
SEC() {
return SEflag(0);
},
SEH() {
return SEflag(5);
},
SEI() {
return SEflag(7);
},
SEN() {
return SEflag(2);
},
SER(a) {
const r = 0xef0f | (destRindex(a, 16, 31) & 0xf0);
return zeroPad(r);
},
SES() {
return SEflag(4);
},
SET() {
return SEflag(6);
},
SEV() {
return SEflag(3);
},
SEZ() {
return SEflag(1);
},
SLEEP() {
return '9588';
},
SPM(a) {
if (typeof a === 'undefined' || a === '') {
return '95e8';
}
else {
if (a !== 'Z+') {
throw 'Bad param to SPM';
}
return '95f8';
}
},
ST(a, b) {
const r = 0x0200 | destRindex(b) | stldXYZ(a);
return zeroPad(r);
},
STD(a, b) {
const r = 0x0200 | destRindex(b) | stldYZq(a);
return zeroPad(r);
},
STS(a, b) {
const k = constValue(a, 0, 65535);
const r = 0x9200 | destRindex(b);
return [zeroPad(r), zeroPad(k)];
},
SUB(a, b) {
const r = 0x1800 | destRindex(a) | srcRindex(b);
return zeroPad(r);
},
SUBI(a, b) {
let r = 0x5000 | (destRindex(a, 16, 31) & 0xf0);
const k = constValue(b);
r |= ((k & 0xf0) << 4) | (k & 0xf);
return zeroPad(r);
},
SWAP(a) {
const r = 0x9402 | destRindex(a);
return zeroPad(r);
},
TST(a, _, byteLoc, l) {
return OPTABLE['AND'](a, a, byteLoc, l);
},
WDR() {
return '95a8';
},
XCH(a, b) {
const r = 0x9204 | destRindex(b);
if (a !== 'Z') {
throw 'Bad param, not Z';
}
return zeroPad(r);
},
};
function passOne(inputdata) {
const lines = inputdata.split('\n');
const commentReg = /[#;].*$/;
const labelReg = /^(\w+):/;
const codeReg = /^\s*(\w+)(?:\s+([^,]+)(?:,\s*(\S+))?)?\s*$/;
let lt;
let res;
let rets;
let instruction;
let byteOffset = 0;
const lableTable = {};
const replacements = {};
const errorTable = [];
const lineTable = [];
for (let idx = 0; idx < lines.length; idx++) {
res = lines[idx].trim();
if (res.length === 0) {
continue;
}
lt = { line: idx + 1, text: res, bytes: [], byteOffset: 0 };
res = res.replace(commentReg, '').trim(); /* strip off comments. */
if (res.length === 0) {
continue;
}
/* check for a label */
rets = res.match(labelReg);
if (rets) {
lableTable[rets[1]] = byteOffset;
res = res.replace(labelReg, '').trim(); /* strip out label. */
}
if (res.length === 0) {
continue;
}
/* Check for a mnemonic line */
const resMatch = res.match(codeReg);
try {
if (resMatch === null) {
throw "doesn't match as code!";
}
if (!resMatch[1]) {
throw 'Empty mnemonic field!';
}
/* do opcode */
instruction = resMatch[1].toUpperCase().trim();
/* This switch is ok for just these three.
* If ever to add more, then need to figure out how to merge all of the
* mnemonics into the OPTABLE. (or build a seperate internal op table)
*/
switch (instruction) {
case '_REPLACE':
replacements[resMatch[2]] = resMatch[3];
continue;
case '_LOC': {
const num = parseInt(resMatch[2]);
if (isNaN(num)) {
throw 'Location is not a number.';
}
if (num & 0x1) {
throw 'Location is odd';
}
byteOffset = num;
continue;
}
case '_IW': {
const num = parseInt(resMatch[2]);
if (isNaN(num)) {
throw 'Immeadiate Word is not a number.';
}
lt.bytes = zeroPad(num);
lt.byteOffset = byteOffset;
byteOffset += 2;
continue;
}
}
if (!(instruction in OPTABLE)) {
throw 'No such instruction: ' + instruction;
}
/* do replacements on parameters. */
if (resMatch[2] in replacements) {
resMatch[2] = replacements[resMatch[2]];
}
if (resMatch[3] in replacements) {
resMatch[3] = replacements[resMatch[3]];
}
const bytes = OPTABLE[instruction](resMatch[2], resMatch[3], byteOffset, lableTable);
lt.byteOffset = byteOffset;
switch (typeof bytes) {
case 'function':
case 'string':
byteOffset += 2;
break;
case 'object' /* assumed as an array. */:
byteOffset += bytes.length * 2;
break;
default:
throw 'unknown return type from optable.';
}
lt.bytes = bytes;
lineTable.push(lt);
}
catch (err) {
errorTable.push('Line ' + idx + ': ' + err);
}
}
return {
labels: lableTable,
errors: errorTable,
lines: lineTable,
};
}
function elementSize(lt) {
return typeof lt.bytes === 'string' ? lt.bytes.length / 2 : lt.bytes.length * 2;
}
/**
* Handle any forward referenced labels that were deferred in passone.
*/
function passTwo(lineTable, labels) {
const errorTable = [];
const lastElement = lineTable[lineTable.length - 1];
const byteSize = lastElement ? lastElement.byteOffset + elementSize(lastElement) : 0;
const resultTable = new Uint8Array(byteSize);
for (const ltEntry of lineTable) {
try {
/* Look for functions left over from passone. */
if (typeof ltEntry.bytes === 'function') {
ltEntry.bytes = ltEntry.bytes(labels);
}
if (ltEntry.bytes instanceof Array &&
ltEntry.bytes.length >= 1 &&
typeof ltEntry.bytes[0] === 'function') {
/* a bit gross. FIXME */
ltEntry.bytes = ltEntry.bytes[0](labels);
}
/* copy bytes out of linetable into the results. */
switch (typeof ltEntry.bytes) {
case 'string':
resultTable[ltEntry.byteOffset + 1] = parseInt(ltEntry.bytes.substr(0, 2), 16);
resultTable[ltEntry.byteOffset] = parseInt(ltEntry.bytes.substr(2, 4), 16);
break;
case 'object' /* also array. */:
if (ltEntry.bytes.length < 1) {
throw 'Empty array in lineTable.';
}
for (let j = 0, bi = ltEntry.byteOffset; j < ltEntry.bytes.length; j++, bi += 2) {
const value = ltEntry.bytes[j];
if (typeof value !== 'string') {
throw 'Not an array of strings.';
}
resultTable[bi + 1] = parseInt(value.substr(0, 2), 16);
resultTable[bi] = parseInt(value.substr(2, 4), 16);
}
break;
default:
throw 'unknown return type from optable.';
}
}
catch (err) {
errorTable.push('Line: ' + ltEntry.line + ': ' + err);
}
}
return { errors: errorTable, bytes: resultTable, lines: lineTable, labels };
}
/**
* The assembler.
*/
export function assemble(input) {
const mid = passOne(input);
if (mid.errors.length > 0) {
return {
bytes: new Uint8Array(0),
errors: mid.errors,
lines: [],
labels: {},
};
}
return passTwo(mid.lines, mid.labels);
}
+51
View File
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Uri Shaked and contributors
import { avrInstruction } from '../cpu/instruction.js';
import { assemble } from './assembler.js';
const BREAK_OPCODE = 0x9598;
export function asmProgram(source) {
const { bytes, errors, lines, labels } = assemble(source);
if (errors.length) {
throw new Error('Assembly failed: ' + errors);
}
return { program: new Uint16Array(bytes.buffer), lines, instructionCount: lines.length, labels };
}
const defaultOnBreak = () => {
throw new Error('BREAK instruction encountered');
};
export class TestProgramRunner {
constructor(cpu, onBreak = defaultOnBreak) {
this.cpu = cpu;
this.onBreak = onBreak;
}
runInstructions(count) {
const { cpu, onBreak } = this;
for (let i = 0; i < count; i++) {
if (cpu.progMem[cpu.pc] === BREAK_OPCODE) {
onBreak === null || onBreak === void 0 ? void 0 : onBreak(cpu);
}
avrInstruction(cpu);
cpu.tick();
}
}
runUntil(predicate, maxIterations = 5000) {
const { cpu, onBreak } = this;
for (let i = 0; i < maxIterations; i++) {
if (cpu.progMem[cpu.pc] === BREAK_OPCODE) {
onBreak === null || onBreak === void 0 ? void 0 : onBreak(cpu);
}
if (predicate(cpu)) {
return;
}
avrInstruction(cpu);
cpu.tick();
}
throw new Error('Test program ran for too long, check your predicate');
}
runToBreak() {
this.runUntil((cpu) => cpu.progMem[cpu.pc] === BREAK_OPCODE);
}
runToAddress(byteAddr) {
this.runUntil((cpu) => cpu.pc * 2 === byteAddr);
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
!function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror"),require("../xml/xml"),require("../javascript/javascript"),require("../css/css")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","../xml/xml","../javascript/javascript","../css/css"],t):t(CodeMirror)}(function(m){"use strict";var l={script:[["lang",/(javascript|babel)/i,"javascript"],["type",/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i,"javascript"],["type",/./,"text/plain"],[null,null,"javascript"]],style:[["lang",/^css$/i,"css"],["type",/^(text\/)?(x-)?(stylesheet|css)$/i,"css"],["type",/./,"text/plain"],[null,null,"css"]]};var a={};function d(t,e){e=t.match(a[t=e]||(a[t]=new RegExp("\\s+"+t+"\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*")));return e?/^\s*(.*?)\s*$/.exec(e[2])[1]:""}function g(t,e){return new RegExp((e?"^":"")+"</\\s*"+t+"\\s*>","i")}function o(t,e){for(var a in t)for(var n=e[a]||(e[a]=[]),l=t[a],o=l.length-1;0<=o;o--)n.unshift(l[o])}m.defineMode("htmlmixed",function(i,t){var c=m.getMode(i,{name:"xml",htmlMode:!0,multilineTagIndentFactor:t.multilineTagIndentFactor,multilineTagIndentPastTag:t.multilineTagIndentPastTag,allowMissingTagName:t.allowMissingTagName}),s={},e=t&&t.tags,a=t&&t.scriptTypes;if(o(l,s),e&&o(e,s),a)for(var n=a.length-1;0<=n;n--)s.script.unshift(["type",a[n].matches,a[n].mode]);function u(t,e){var a,o,r,n=c.token(t,e.htmlState),l=/\btag\b/.test(n);return l&&!/[<>\s\/]/.test(t.current())&&(a=e.htmlState.tagName&&e.htmlState.tagName.toLowerCase())&&s.hasOwnProperty(a)?e.inTag=a+" ":e.inTag&&l&&/>$/.test(t.current())?(a=/^([\S]+) (.*)/.exec(e.inTag),e.inTag=null,l=">"==t.current()&&function(t,e){for(var a=0;a<t.length;a++){var n=t[a];if(!n[0]||n[1].test(d(e,n[0])))return n[2]}}(s[a[1]],a[2]),l=m.getMode(i,l),o=g(a[1],!0),r=g(a[1],!1),e.token=function(t,e){return t.match(o,!1)?(e.token=u,e.localState=e.localMode=null):(a=t,n=r,t=e.localMode.token(t,e.localState),e=a.current(),-1<(l=e.search(n))?a.backUp(e.length-l):e.match(/<\/?$/)&&(a.backUp(e.length),a.match(n,!1)||a.match(e)),t);var a,n,l},e.localMode=l,e.localState=m.startState(l,c.indent(e.htmlState,"",""))):e.inTag&&(e.inTag+=t.current(),t.eol()&&(e.inTag+=" ")),n}return{startState:function(){return{token:u,inTag:null,localMode:null,localState:null,htmlState:m.startState(c)}},copyState:function(t){var e;return t.localState&&(e=m.copyState(t.localMode,t.localState)),{token:t.token,inTag:t.inTag,localMode:t.localMode,localState:e,htmlState:m.copyState(c,t.htmlState)}},token:function(t,e){return e.token(t,e)},indent:function(t,e,a){return!t.localMode||/^\s*<\//.test(e)?c.indent(t.htmlState,e,a):t.localMode.indent?t.localMode.indent(t.localState,e,a):m.Pass},innerMode:function(t){return{state:t.localState||t.htmlState,mode:t.localMode||c}}}},"xml","javascript","css"),m.defineMIME("text/html","htmlmixed")});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
export {};
+6
View File
@@ -0,0 +1,6 @@
import { SimulationClock } from './simulation-clock.js';
export class MockClock extends SimulationClock {
advance(deltaMicros) {
this.tick(this.nanos + deltaMicros * 1000);
}
}
+92
View File
@@ -0,0 +1,92 @@
export class ClockAlarm {
constructor(clock, callback) {
this.clock = clock;
this.callback = callback;
this.next = null;
this.nanos = 0;
this.scheduled = false;
}
schedule(deltaNanos) {
if (this.scheduled) {
this.cancel();
}
this.clock.linkAlarm(deltaNanos, this);
}
cancel() {
this.clock.unlinkAlarm(this);
this.scheduled = false;
}
}
export class SimulationClock {
constructor(frequency = 125e6) {
this.frequency = frequency;
this.nextAlarm = null;
this.nanosCounter = 0;
}
get nanos() {
return this.nanosCounter;
}
get micros() {
return this.nanos / 1000;
}
createAlarm(callback) {
return new ClockAlarm(this, callback);
}
linkAlarm(nanos, alarm) {
alarm.nanos = this.nanos + nanos;
let alarmListItem = this.nextAlarm;
let lastItem = null;
while (alarmListItem && alarmListItem.nanos < alarm.nanos) {
lastItem = alarmListItem;
alarmListItem = alarmListItem.next;
}
if (lastItem) {
lastItem.next = alarm;
alarm.next = alarmListItem;
}
else {
this.nextAlarm = alarm;
alarm.next = alarmListItem;
}
alarm.scheduled = true;
return alarm;
}
unlinkAlarm(alarm) {
let alarmListItem = this.nextAlarm;
if (!alarmListItem) {
return false;
}
let lastItem = null;
while (alarmListItem) {
if (alarmListItem === alarm) {
if (lastItem) {
lastItem.next = alarmListItem.next;
}
else {
this.nextAlarm = alarmListItem.next;
}
return true;
}
lastItem = alarmListItem;
alarmListItem = alarmListItem.next;
}
return false;
}
tick(deltaNanos) {
const targetNanos = this.nanosCounter + deltaNanos;
let alarm = this.nextAlarm;
while (alarm && alarm.nanos <= targetNanos) {
this.nextAlarm = alarm.next;
this.nanosCounter = alarm.nanos;
alarm.callback();
alarm = this.nextAlarm;
}
this.nanosCounter = targetNanos;
}
get nanosToNextAlarm() {
if (this.nextAlarm) {
return this.nextAlarm.nanos - this.nanos;
}
return 0;
}
}
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
import { STOP_REPLY_SIGINT, STOP_REPLY_TRAP } from './gdb-server.js';
import { gdbChecksum, gdbMessage } from './gdb-utils.js';
export class GDBConnection {
constructor(server, onResponse) {
this.server = server;
this.onResponse = onResponse;
this.target = this.server.target;
this.buf = '';
server.addConnection(this);
onResponse('+');
}
feedData(data) {
const { onResponse } = this;
if (data.charCodeAt(0) === 3) {
this.server.info('BREAK');
this.target.stop();
onResponse(gdbMessage(STOP_REPLY_SIGINT));
data = data.slice(1);
}
this.buf += data;
for (;;) {
const dolla = this.buf.indexOf('$');
const hash = this.buf.indexOf('#', dolla + 1);
if (dolla < 0 || hash < 0 || hash + 2 > this.buf.length) {
return;
}
const cmd = this.buf.substring(dolla + 1, hash);
const cksum = this.buf.substr(hash + 1, 2);
this.buf = this.buf.substr(hash + 2);
if (gdbChecksum(cmd) !== cksum) {
this.server.warn(`GDB checksum error in message: ${cmd}`);
onResponse('-');
}
else {
onResponse('+');
this.server.debug(`>${cmd}`);
const response = this.server.processGDBMessage(cmd);
if (response) {
this.server.debug(`<${response}`);
onResponse(response);
}
}
}
}
onBreakpoint() {
try {
this.onResponse(gdbMessage(STOP_REPLY_TRAP));
}
catch (e) {
this.server.removeConnection(this);
}
}
}
+260
View File
@@ -0,0 +1,260 @@
/**
* RP2040 GDB Server
*
* Copyright (C) 2021, Uri Shaked
*/
import { SYSM_CONTROL, SYSM_MSP, SYSM_PRIMASK, SYSM_PSP } from '../cortex-m0-core.js';
import { ConsoleLogger, LogLevel } from '../utils/logging.js';
import { decodeHexBuf, encodeHexBuf, encodeHexByte, encodeHexUint32, gdbMessage, } from './gdb-utils.js';
export const STOP_REPLY_SIGINT = 'S02';
export const STOP_REPLY_TRAP = 'S05';
/* string value: armv6m-none-unknown-eabi */
const lldbTriple = '61726d76366d2d6e6f6e652d756e6b6e6f776e2d65616269';
const registers = [
`name:r0;bitsize:32;offset:0;encoding:int;format:hex;set:General Purpose Registers;generic:arg1;gcc:0;dwarf:0;`,
`name:r1;bitsize:32;offset:4;encoding:int;format:hex;set:General Purpose Registers;generic:arg2;gcc:1;dwarf:1;`,
`name:r2;bitsize:32;offset:8;encoding:int;format:hex;set:General Purpose Registers;generic:arg3;gcc:2;dwarf:2;`,
`name:r3;bitsize:32;offset:12;encoding:int;format:hex;set:General Purpose Registers;generic:arg4;gcc:3;dwarf:3;`,
`name:r4;bitsize:32;offset:16;encoding:int;format:hex;set:General Purpose Registers;gcc:4;dwarf:4;`,
`name:r5;bitsize:32;offset:20;encoding:int;format:hex;set:General Purpose Registers;gcc:5;dwarf:5;`,
`name:r6;bitsize:32;offset:24;encoding:int;format:hex;set:General Purpose Registers;gcc:6;dwarf:6;`,
`name:r7;bitsize:32;offset:28;encoding:int;format:hex;set:General Purpose Registers;gcc:7;dwarf:7;`,
`name:r8;bitsize:32;offset:32;encoding:int;format:hex;set:General Purpose Registers;gcc:8;dwarf:8;`,
`name:r9;bitsize:32;offset:36;encoding:int;format:hex;set:General Purpose Registers;gcc:9;dwarf:9;`,
`name:r10;bitsize:32;offset:40;encoding:int;format:hex;set:General Purpose Registers;gcc:10;dwarf:10;`,
`name:r11;bitsize:32;offset:44;encoding:int;format:hex;set:General Purpose Registers;generic:fp;gcc:11;dwarf:11;`,
`name:r12;bitsize:32;offset:48;encoding:int;format:hex;set:General Purpose Registers;gcc:12;dwarf:12;`,
`name:sp;bitsize:32;offset:52;encoding:int;format:hex;set:General Purpose Registers;generic:sp;alt-name:r13;gcc:13;dwarf:13;`,
`name:lr;bitsize:32;offset:56;encoding:int;format:hex;set:General Purpose Registers;generic:ra;alt-name:r14;gcc:14;dwarf:14;`,
`name:pc;bitsize:32;offset:60;encoding:int;format:hex;set:General Purpose Registers;generic:pc;alt-name:r15;gcc:15;dwarf:15;`,
`name:cpsr;bitsize:32;offset:64;encoding:int;format:hex;set:General Purpose Registers;generic:flags;alt-name:psr;gcc:16;dwarf:16;`,
];
const targetXML = `<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target version="1.0">
<architecture>arm</architecture>
<feature name="org.gnu.gdb.arm.m-profile">
<reg name="r0" bitsize="32" regnum="0" save-restore="yes" type="int" group="general"/>
<reg name="r1" bitsize="32" regnum="1" save-restore="yes" type="int" group="general"/>
<reg name="r2" bitsize="32" regnum="2" save-restore="yes" type="int" group="general"/>
<reg name="r3" bitsize="32" regnum="3" save-restore="yes" type="int" group="general"/>
<reg name="r4" bitsize="32" regnum="4" save-restore="yes" type="int" group="general"/>
<reg name="r5" bitsize="32" regnum="5" save-restore="yes" type="int" group="general"/>
<reg name="r6" bitsize="32" regnum="6" save-restore="yes" type="int" group="general"/>
<reg name="r7" bitsize="32" regnum="7" save-restore="yes" type="int" group="general"/>
<reg name="r8" bitsize="32" regnum="8" save-restore="yes" type="int" group="general"/>
<reg name="r9" bitsize="32" regnum="9" save-restore="yes" type="int" group="general"/>
<reg name="r10" bitsize="32" regnum="10" save-restore="yes" type="int" group="general"/>
<reg name="r11" bitsize="32" regnum="11" save-restore="yes" type="int" group="general"/>
<reg name="r12" bitsize="32" regnum="12" save-restore="yes" type="int" group="general"/>
<reg name="sp" bitsize="32" regnum="13" save-restore="yes" type="data_ptr" group="general"/>
<reg name="lr" bitsize="32" regnum="14" save-restore="yes" type="int" group="general"/>
<reg name="pc" bitsize="32" regnum="15" save-restore="yes" type="code_ptr" group="general"/>
<reg name="xPSR" bitsize="32" regnum="16" save-restore="yes" type="int" group="general"/>
</feature>
<feature name="org.gnu.gdb.arm.m-system">
<reg name="msp" bitsize="32" regnum="17" save-restore="yes" type="data_ptr" group="system"/>
<reg name="psp" bitsize="32" regnum="18" save-restore="yes" type="data_ptr" group="system"/>
<reg name="primask" bitsize="1" regnum="19" save-restore="yes" type="int8" group="system"/>
<reg name="basepri" bitsize="8" regnum="20" save-restore="yes" type="int8" group="system"/>
<reg name="faultmask" bitsize="1" regnum="21" save-restore="yes" type="int8" group="system"/>
<reg name="control" bitsize="2" regnum="22" save-restore="yes" type="int8" group="system"/>
</feature>
</target>`;
const LOG_NAME = 'GDBServer';
export class GDBServer {
constructor(target) {
this.target = target;
this.logger = new ConsoleLogger(LogLevel.Warn, true);
this.connections = new Set();
}
processGDBMessage(cmd) {
const { rp2040 } = this.target;
const { core } = rp2040;
if (cmd === 'Hg0') {
return gdbMessage('OK');
}
switch (cmd[0]) {
case '?':
return gdbMessage(STOP_REPLY_TRAP);
case 'q':
// Query things
if (cmd.startsWith('qSupported:')) {
return gdbMessage('PacketSize=4000;vContSupported+;qXfer:features:read+');
}
if (cmd === 'qAttached') {
return gdbMessage('1');
}
if (cmd.startsWith('qXfer:features:read:target.xml')) {
return gdbMessage('l' + targetXML);
}
if (cmd.startsWith('qRegisterInfo')) {
const index = parseInt(cmd.substring(13), 16);
const register = registers[index];
if (register) {
return gdbMessage(register);
}
else {
return gdbMessage(`E45`);
}
}
if (cmd === 'qHostInfo') {
return gdbMessage(`triple:${lldbTriple};endian:little;ptrsize:4;`);
}
if (cmd === 'qProcessInfo') {
return gdbMessage('pid:1;endian:little;ptrsize:4;');
}
return gdbMessage('');
case 'v':
if (cmd === 'vCont?') {
return gdbMessage('vCont;c;C;s;S');
}
if (cmd.startsWith('vCont;c')) {
if (!this.target.executing) {
this.target.execute();
}
return;
}
if (cmd.startsWith('vCont;s')) {
rp2040.step();
const registerStatus = [];
for (let i = 0; i < 17; i++) {
const value = i === 16 ? core.xPSR : core.registers[i];
registerStatus.push(`${encodeHexByte(i)}:${encodeHexUint32(value)}`);
}
return gdbMessage(`T05${registerStatus.join(';')};reason:trace;`);
}
break;
case 'c':
if (!this.target.executing) {
this.target.execute();
}
return gdbMessage('OK');
case 'g': {
// Read registers
const buf = new Uint32Array(17);
buf.set(core.registers);
buf[16] = core.xPSR;
return gdbMessage(encodeHexBuf(new Uint8Array(buf.buffer)));
}
case 'p': {
// Read register
const registerIndex = parseInt(cmd.substr(1), 16);
if (registerIndex >= 0 && registerIndex <= 15) {
return gdbMessage(encodeHexUint32(core.registers[registerIndex]));
}
const specialRegister = (sysm) => gdbMessage(encodeHexUint32(core.readSpecialRegister(sysm)));
switch (registerIndex) {
case 0x10:
return gdbMessage(encodeHexUint32(core.xPSR));
case 0x11:
return specialRegister(SYSM_MSP);
case 0x12:
return specialRegister(SYSM_PSP);
case 0x13:
return specialRegister(SYSM_PRIMASK);
case 0x14:
this.logger.warn(LOG_NAME, 'TODO BASEPRI');
return gdbMessage(encodeHexUint32(0)); // TODO BASEPRI
case 0x15:
this.logger.warn(LOG_NAME, 'TODO faultmask');
return gdbMessage(encodeHexUint32(0)); // TODO faultmask
case 0x16:
return specialRegister(SYSM_CONTROL);
}
break;
}
case 'P': {
// Write register
const params = cmd.substring(1).split('=');
const registerIndex = parseInt(params[0], 16);
const registerValue = params[1].trim();
const registerBytes = registerIndex > 0x12 ? 1 : 4;
const decodedValue = decodeHexBuf(registerValue);
if (registerIndex < 0 || registerIndex > 0x16 || decodedValue.length !== registerBytes) {
return gdbMessage('E00');
}
const valueBuffer = new Uint8Array(4);
valueBuffer.set(decodedValue.slice(0, 4));
const value = new DataView(valueBuffer.buffer).getUint32(0, true);
switch (registerIndex) {
case 0x10:
core.xPSR = value;
break;
case 0x11:
core.writeSpecialRegister(SYSM_MSP, value);
break;
case 0x12:
core.writeSpecialRegister(SYSM_PSP, value);
break;
case 0x13:
core.writeSpecialRegister(SYSM_PRIMASK, value);
break;
case 0x14:
this.logger.warn(LOG_NAME, 'TODO BASEPRI');
break; // TODO BASEPRI
case 0x15:
this.logger.warn(LOG_NAME, 'TODO faultmask');
break; // TODO faultmask
case 0x16:
core.writeSpecialRegister(SYSM_CONTROL, value);
break;
default:
core.registers[registerIndex] = value;
break;
}
return gdbMessage('OK');
}
case 'm': {
// Read memory
const params = cmd.substr(1).split(',');
const address = parseInt(params[0], 16);
const length = parseInt(params[1], 16);
let result = '';
for (let i = 0; i < length; i++) {
result += encodeHexByte(rp2040.readUint8(address + i));
}
return gdbMessage(result);
}
case 'M': {
// Write memory
const params = cmd.substr(1).split(/[,:]/);
const address = parseInt(params[0], 16);
const length = parseInt(params[1], 16);
const data = decodeHexBuf(params[2].substr(0, length * 2));
for (let i = 0; i < data.length; i++) {
this.debug(`Write ${data[i].toString(16)} to ${(address + i).toString(16)}`);
rp2040.writeUint8(address + i, data[i]);
}
return gdbMessage('OK');
}
}
return gdbMessage('');
}
addConnection(connection) {
const { rp2040 } = this.target;
this.connections.add(connection);
rp2040.onBreak = () => {
this.target.stop();
rp2040.core.PC -= rp2040.core.breakRewind;
for (const connection of this.connections) {
connection.onBreakpoint();
}
};
}
removeConnection(connection) {
this.connections.delete(connection);
}
debug(msg) {
this.logger.debug(LOG_NAME, msg);
}
info(msg) {
this.logger.info(LOG_NAME, msg);
}
warn(msg) {
this.logger.warn(LOG_NAME, msg);
}
error(msg) {
this.logger.error(LOG_NAME, msg);
}
}
+1
View File
@@ -0,0 +1 @@
export {};
+30
View File
@@ -0,0 +1,30 @@
import { createServer } from 'net';
import { GDBConnection } from './gdb-connection.js';
import { GDBServer } from './gdb-server.js';
export class GDBTCPServer extends GDBServer {
constructor(target, port = 3333) {
super(target);
this.port = port;
this.socketServer = createServer();
this.socketServer.listen(port);
this.socketServer.on('connection', (socket) => this.handleConnection(socket));
}
handleConnection(socket) {
this.info('GDB connected');
socket.setNoDelay(true);
const connection = new GDBConnection(this, (data) => {
socket.write(data);
});
socket.on('data', (data) => {
connection.feedData(data.toString('utf-8'));
});
socket.on('error', (err) => {
this.removeConnection(connection);
this.error(`GDB socket error ${err}`);
});
socket.on('close', () => {
this.removeConnection(connection);
this.info('GDB disconnected');
});
}
}
+36
View File
@@ -0,0 +1,36 @@
export function encodeHexByte(value) {
return (value >> 4).toString(16) + (value & 0xf).toString(16);
}
export function encodeHexBuf(buf) {
return Array.from(buf).map(encodeHexByte).join('');
}
export function encodeHexUint32BE(value) {
return encodeHexBuf(new Uint8Array([(value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff]));
}
export function encodeHexUint32(value) {
const buf = new Uint32Array([value]);
return encodeHexBuf(new Uint8Array(buf.buffer));
}
export function decodeHexBuf(encoded) {
const result = new Uint8Array(encoded.length / 2);
for (let i = 0; i < result.length; i++) {
result[i] = parseInt(encoded.substr(i * 2, 2), 16);
}
return result;
}
export function decodeHexUint32Array(encoded) {
return new Uint32Array(decodeHexBuf(encoded).buffer);
}
export function decodeHexUint32(encoded) {
return decodeHexUint32Array(encoded)[0];
}
export function gdbChecksum(text) {
const value = text
.split('')
.map((c) => c.charCodeAt(0))
.reduce((a, b) => a + b, 0) & 0xff;
return encodeHexByte(value);
}
export function gdbMessage(value) {
return `$${value}#${gdbChecksum(value)}`;
}
+218
View File
@@ -0,0 +1,218 @@
import { WaitType } from './peripherals/pio.js';
export var GPIOPinState;
(function (GPIOPinState) {
GPIOPinState[GPIOPinState["Low"] = 0] = "Low";
GPIOPinState[GPIOPinState["High"] = 1] = "High";
GPIOPinState[GPIOPinState["Input"] = 2] = "Input";
GPIOPinState[GPIOPinState["InputPullUp"] = 3] = "InputPullUp";
GPIOPinState[GPIOPinState["InputPullDown"] = 4] = "InputPullDown";
GPIOPinState[GPIOPinState["InputBusKeeper"] = 5] = "InputBusKeeper";
})(GPIOPinState || (GPIOPinState = {}));
export const FUNCTION_PWM = 4;
export const FUNCTION_SIO = 5;
export const FUNCTION_PIO0 = 6;
export const FUNCTION_PIO1 = 7;
function applyOverride(value, overrideType) {
switch (overrideType) {
case 0:
return value;
case 1:
return !value;
case 2:
return false;
case 3:
return true;
}
console.error('applyOverride received invalid override type', overrideType);
return value;
}
const IRQ_EDGE_HIGH = 1 << 3;
const IRQ_EDGE_LOW = 1 << 2;
const IRQ_LEVEL_HIGH = 1 << 1;
const IRQ_LEVEL_LOW = 1 << 0;
export class GPIOPin {
constructor(rp2040, index, name = index.toString()) {
this.rp2040 = rp2040;
this.index = index;
this.name = name;
this.rawInputValue = false;
this.lastValue = this.value;
this.ctrl = 0x1f;
this.padValue = 0b0110110;
this.irqEnableMask = 0;
this.irqForceMask = 0;
this.irqStatus = 0;
this.listeners = new Set();
}
get rawInterrupt() {
return !!((this.irqStatus & this.irqEnableMask) | this.irqForceMask);
}
get isSlewFast() {
return !!(this.padValue & 1);
}
get schmittEnabled() {
return !!(this.padValue & 2);
}
get pulldownEnabled() {
return !!(this.padValue & 4);
}
get pullupEnabled() {
return !!(this.padValue & 8);
}
get driveStrength() {
return (this.padValue >> 4) & 0x3;
}
get inputEnable() {
return !!(this.padValue & 0x40);
}
get outputDisable() {
return !!(this.padValue & 0x80);
}
get functionSelect() {
return this.ctrl & 0x1f;
}
get outputOverride() {
return (this.ctrl >> 8) & 0x3;
}
get outputEnableOverride() {
return (this.ctrl >> 12) & 0x3;
}
get inputOverride() {
return (this.ctrl >> 16) & 0x3;
}
get irqOverride() {
return (this.ctrl >> 28) & 0x3;
}
get rawOutputEnable() {
const { index, rp2040, functionSelect } = this;
const bitmask = 1 << index;
switch (functionSelect) {
case FUNCTION_PWM:
return !!(rp2040.pwm.gpioDirection & bitmask);
case FUNCTION_SIO:
return !!(rp2040.sio.gpioOutputEnable & bitmask);
case FUNCTION_PIO0:
return !!(rp2040.pio[0].pinDirections & bitmask);
case FUNCTION_PIO1:
return !!(rp2040.pio[1].pinDirections & bitmask);
default:
return false;
}
}
get rawOutputValue() {
const { index, rp2040, functionSelect } = this;
const bitmask = 1 << index;
switch (functionSelect) {
case FUNCTION_PWM:
return !!(rp2040.pwm.gpioValue & bitmask);
case FUNCTION_SIO:
return !!(rp2040.sio.gpioValue & bitmask);
case FUNCTION_PIO0:
return !!(rp2040.pio[0].pinValues & bitmask);
case FUNCTION_PIO1:
return !!(rp2040.pio[1].pinValues & bitmask);
default:
return false;
}
}
get inputValue() {
return applyOverride(this.rawInputValue && this.inputEnable, this.inputOverride);
}
get irqValue() {
return applyOverride(this.rawInterrupt, this.irqOverride);
}
get outputEnable() {
return applyOverride(this.rawOutputEnable, this.outputEnableOverride);
}
get outputValue() {
return applyOverride(this.rawOutputValue, this.outputOverride);
}
/**
* Returns the STATUS register value for the pin, as outlined in section 2.19.6 of the datasheet
*/
get status() {
const irqToProc = this.irqValue ? 1 << 26 : 0;
const irqFromPad = this.rawInterrupt ? 1 << 24 : 0;
const inToPeri = this.inputValue ? 1 << 19 : 0;
const inFromPad = this.rawInputValue ? 1 << 17 : 0;
const oeToPad = this.outputEnable ? 1 << 13 : 0;
const oeFromPeri = this.rawOutputEnable ? 1 << 12 : 0;
const outToPad = this.outputValue ? 1 << 9 : 0;
const outFromPeri = this.rawOutputValue ? 1 << 8 : 0;
return (irqToProc | irqFromPad | inToPeri | inFromPad | oeToPad | oeFromPeri | outToPad | outFromPeri);
}
get value() {
if (this.outputEnable) {
return this.outputValue ? GPIOPinState.High : GPIOPinState.Low;
}
else {
// TODO: check what happens when we enable both pullup/pulldown
// ANSWER: It is valid, see: 2.19.4.1. Bus Keeper Mode, datasheet p240
if (this.pulldownEnabled && this.pullupEnabled) {
// Pull high when high, pull low when low:
return GPIOPinState.InputBusKeeper;
}
else if (this.pulldownEnabled) {
return GPIOPinState.InputPullDown;
}
else if (this.pullupEnabled) {
return GPIOPinState.InputPullUp;
}
return GPIOPinState.Input;
}
}
setInputValue(value) {
this.rawInputValue = value;
const prevIrqValue = this.irqValue;
if (value && this.inputEnable) {
this.irqStatus |= IRQ_EDGE_HIGH | IRQ_LEVEL_HIGH;
this.irqStatus &= ~IRQ_LEVEL_LOW;
}
else {
this.irqStatus |= IRQ_EDGE_LOW | IRQ_LEVEL_LOW;
this.irqStatus &= ~IRQ_LEVEL_HIGH;
}
if (this.irqValue !== prevIrqValue) {
this.rp2040.updateIOInterrupt();
}
if (this.functionSelect === FUNCTION_PWM) {
this.rp2040.pwm.gpioOnInput(this.index);
}
for (const pio of this.rp2040.pio) {
for (const machine of pio.machines) {
if (machine.enabled &&
machine.waiting &&
machine.waitType === WaitType.Pin &&
machine.waitIndex === this.index) {
machine.checkWait();
}
}
}
}
checkForUpdates() {
const { lastValue, value } = this;
if (value !== lastValue) {
this.lastValue = value;
for (const listener of this.listeners) {
listener(value, lastValue);
}
}
}
refreshInput() {
this.setInputValue(this.rawInputValue);
}
updateIRQValue(value) {
if (value & IRQ_EDGE_LOW && this.irqStatus & IRQ_EDGE_LOW) {
this.irqStatus &= ~IRQ_EDGE_LOW;
this.rp2040.updateIOInterrupt();
}
if (value & IRQ_EDGE_HIGH && this.irqStatus & IRQ_EDGE_HIGH) {
this.irqStatus &= ~IRQ_EDGE_HIGH;
this.rp2040.updateIOInterrupt();
}
}
addListener(callback) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
}
+15
View File
@@ -0,0 +1,15 @@
export { GDBConnection } from './gdb/gdb-connection.js';
export { GDBServer } from './gdb/gdb-server.js';
export { GPIOPin, GPIOPinState } from './gpio-pin.js';
export { I2CMode, I2CSpeed, RPI2C } from './peripherals/i2c.js';
export { BasePeripheral } from './peripherals/peripheral.js';
export { RPPIO, StateMachine } from './peripherals/pio.js';
export { RPUSBController } from './peripherals/usb.js';
export { RP2040 } from './rp2040.js';
export { Simulator } from './simulator.js';
export { USBCDC } from './usb/cdc.js';
export { DataDirection, DescriptorType, SetupRecipient, SetupRequest, SetupType, } from './usb/interfaces.js';
export { createSetupPacket, getDescriptorPacket, setDeviceAddressPacket, setDeviceConfigurationPacket, } from './usb/setup.js';
export { parseSetupPacket, } from './usb/usb-device.js';
export { FIFO } from './utils/fifo.js';
export { ConsoleLogger, LogLevel } from './utils/logging.js';
+145
View File
@@ -0,0 +1,145 @@
import { s32, u32 } from './utils/bit.js';
export class InterpolatorConfig {
constructor(value) {
this.shift = 0;
this.maskLSB = 0;
this.maskMSB = 0;
this.signed = false;
this.crossInput = false;
this.crossResult = false;
this.addRaw = false;
this.forceMSB = 0;
this.blend = false;
this.clamp = false;
this.overf0 = false;
this.overf1 = false;
this.overf = false;
this.shift = (value >>> 0) & 0b11111;
this.maskLSB = (value >>> 5) & 0b11111;
this.maskMSB = (value >>> 10) & 0b11111;
this.signed = Boolean((value >>> 15) & 1);
this.crossInput = Boolean((value >>> 16) & 1);
this.crossResult = Boolean((value >>> 17) & 1);
this.addRaw = Boolean((value >>> 18) & 1);
this.forceMSB = (value >>> 19) & 0b11;
this.blend = Boolean((value >>> 21) & 1);
this.clamp = Boolean((value >>> 22) & 1);
this.overf0 = Boolean((value >>> 23) & 1);
this.overf1 = Boolean((value >>> 24) & 1);
this.overf = Boolean((value >>> 25) & 1);
}
toUint32() {
return (((this.shift & 0b11111) << 0) |
((this.maskLSB & 0b11111) << 5) |
((this.maskMSB & 0b11111) << 10) |
((Number(this.signed) & 1) << 15) |
((Number(this.crossInput) & 1) << 16) |
((Number(this.crossResult) & 1) << 17) |
((Number(this.addRaw) & 1) << 18) |
((this.forceMSB & 0b11) << 19) |
((Number(this.blend) & 1) << 21) |
((Number(this.clamp) & 1) << 22) |
((Number(this.overf0) & 1) << 23) |
((Number(this.overf1) & 1) << 24) |
((Number(this.overf) & 1) << 25));
}
}
export class Interpolator {
constructor(index) {
this.index = index;
this.accum0 = 0;
this.accum1 = 0;
this.base0 = 0;
this.base1 = 0;
this.base2 = 0;
this.ctrl0 = 0;
this.ctrl1 = 0;
this.result0 = 0;
this.result1 = 0;
this.result2 = 0;
this.smresult0 = 0;
this.smresult1 = 0;
this.update();
}
update() {
const N = this.index;
const ctrl0 = new InterpolatorConfig(this.ctrl0);
const ctrl1 = new InterpolatorConfig(this.ctrl1);
const do_clamp = ctrl0.clamp && N == 1;
const do_blend = ctrl0.blend && N == 0;
ctrl0.clamp = do_clamp;
ctrl0.blend = do_blend;
ctrl1.clamp = false;
ctrl1.blend = false;
ctrl1.overf0 = false;
ctrl1.overf1 = false;
ctrl1.overf = false;
const input0 = s32(ctrl0.crossInput ? this.accum1 : this.accum0);
const input1 = s32(ctrl1.crossInput ? this.accum0 : this.accum1);
const msbmask0 = ctrl0.maskMSB == 31 ? 0xffffffff : (1 << (ctrl0.maskMSB + 1)) - 1;
const msbmask1 = ctrl1.maskMSB == 31 ? 0xffffffff : (1 << (ctrl1.maskMSB + 1)) - 1;
const mask0 = msbmask0 & ~((1 << ctrl0.maskLSB) - 1);
const mask1 = msbmask1 & ~((1 << ctrl1.maskLSB) - 1);
const uresult0 = (input0 >>> ctrl0.shift) & mask0;
const uresult1 = (input1 >>> ctrl1.shift) & mask1;
const overf0 = Boolean((input0 >>> ctrl0.shift) & ~msbmask0);
const overf1 = Boolean((input1 >>> ctrl1.shift) & ~msbmask1);
const overf = overf0 || overf1;
const sextmask0 = uresult0 & (1 << ctrl0.maskMSB) ? -1 << ctrl0.maskMSB : 0;
const sextmask1 = uresult1 & (1 << ctrl1.maskMSB) ? -1 << ctrl1.maskMSB : 0;
const sresult0 = uresult0 | sextmask0;
const sresult1 = uresult1 | sextmask1;
const result0 = ctrl0.signed ? sresult0 : uresult0;
const result1 = ctrl1.signed ? sresult1 : uresult1;
const addresult0 = this.base0 + (ctrl0.addRaw ? input0 : result0);
const addresult1 = this.base1 + (ctrl1.addRaw ? input1 : result1);
const addresult2 = this.base2 + result0 + (do_blend ? 0 : result1);
const uclamp0 = u32(result0) < u32(this.base0)
? this.base0
: u32(result0) > u32(this.base1)
? this.base1
: result0;
const sclamp0 = s32(result0) < s32(this.base0)
? this.base0
: s32(result0) > s32(this.base1)
? this.base1
: result0;
const clamp0 = ctrl0.signed ? sclamp0 : uclamp0;
const alpha1 = result1 & 0xff;
const ublend1 = u32(this.base0) + (Math.floor((alpha1 * (u32(this.base1) - u32(this.base0))) / 256) | 0);
const sblend1 = s32(this.base0) + (Math.floor((alpha1 * (s32(this.base1) - s32(this.base0))) / 256) | 0);
const blend1 = ctrl1.signed ? sblend1 : ublend1;
this.smresult0 = u32(result0);
this.smresult1 = u32(result1);
this.result0 = u32(do_blend ? alpha1 : (do_clamp ? clamp0 : addresult0) | (ctrl0.forceMSB << 28));
this.result1 = u32((do_blend ? blend1 : addresult1) | (ctrl0.forceMSB << 28));
this.result2 = u32(addresult2);
ctrl0.overf0 = overf0;
ctrl0.overf1 = overf1;
ctrl0.overf = overf;
this.ctrl0 = ctrl0.toUint32();
this.ctrl1 = ctrl1.toUint32();
}
writeback() {
const ctrl0 = new InterpolatorConfig(this.ctrl0);
const ctrl1 = new InterpolatorConfig(this.ctrl1);
this.accum0 = u32(ctrl0.crossResult ? this.result1 : this.result0);
this.accum1 = u32(ctrl1.crossResult ? this.result0 : this.result1);
this.update();
}
setBase01(value) {
const N = this.index;
const ctrl0 = new InterpolatorConfig(this.ctrl0);
const ctrl1 = new InterpolatorConfig(this.ctrl1);
const do_blend = ctrl0.blend && N == 0;
const input0 = value & 0xffff;
const input1 = (value >>> 16) & 0xffff;
const sextmask0 = input0 & (1 << 15) ? -1 << 15 : 0;
const sextmask1 = input1 & (1 << 15) ? -1 << 15 : 0;
const base0 = (do_blend ? ctrl1.signed : ctrl0.signed) ? input0 | sextmask0 : input0;
const base1 = ctrl1.signed ? input1 | sextmask1 : input1;
this.base0 = u32(base0);
this.base1 = u32(base1);
this.update();
}
}
+30
View File
@@ -0,0 +1,30 @@
export var IRQ;
(function (IRQ) {
IRQ[IRQ["TIMER_0"] = 0] = "TIMER_0";
IRQ[IRQ["TIMER_1"] = 1] = "TIMER_1";
IRQ[IRQ["TIMER_2"] = 2] = "TIMER_2";
IRQ[IRQ["TIMER_3"] = 3] = "TIMER_3";
IRQ[IRQ["PWM_WRAP"] = 4] = "PWM_WRAP";
IRQ[IRQ["USBCTRL"] = 5] = "USBCTRL";
IRQ[IRQ["XIP"] = 6] = "XIP";
IRQ[IRQ["PIO0_IRQ0"] = 7] = "PIO0_IRQ0";
IRQ[IRQ["PIO0_IRQ1"] = 8] = "PIO0_IRQ1";
IRQ[IRQ["PIO1_IRQ0"] = 9] = "PIO1_IRQ0";
IRQ[IRQ["PIO1_IRQ1"] = 10] = "PIO1_IRQ1";
IRQ[IRQ["DMA_IRQ0"] = 11] = "DMA_IRQ0";
IRQ[IRQ["DMA_IRQ1"] = 12] = "DMA_IRQ1";
IRQ[IRQ["IO_BANK0"] = 13] = "IO_BANK0";
IRQ[IRQ["IO_QSPI"] = 14] = "IO_QSPI";
IRQ[IRQ["SIO_PROC0"] = 15] = "SIO_PROC0";
IRQ[IRQ["SIO_PROC1"] = 16] = "SIO_PROC1";
IRQ[IRQ["CLOCKS"] = 17] = "CLOCKS";
IRQ[IRQ["SPI0"] = 18] = "SPI0";
IRQ[IRQ["SPI1"] = 19] = "SPI1";
IRQ[IRQ["UART0"] = 20] = "UART0";
IRQ[IRQ["UART1"] = 21] = "UART1";
IRQ[IRQ["ADC_FIFO"] = 22] = "ADC_FIFO";
IRQ[IRQ["I2C0"] = 23] = "I2C0";
IRQ[IRQ["I2C1"] = 24] = "I2C1";
IRQ[IRQ["RTC"] = 25] = "RTC";
})(IRQ || (IRQ = {}));
export const MAX_HARDWARE_IRQ = IRQ.RTC;
+261
View File
@@ -0,0 +1,261 @@
import { IRQ } from '../irq.js';
import { FIFO } from '../utils/fifo.js';
import { DREQChannel } from './dma.js';
import { BasePeripheral } from './peripheral.js';
const CS = 0x00; // ADC Control and Status
const RESULT = 0x04; // Result of most recent ADC conversion
const FCS = 0x08; // FIFO control and status
const FIFO_REG = 0x0c; // Conversion result FIFO
const DIV = 0x10; // Clock divider.0x14 INTR Raw Interrupts
const INTR = 0x14; // Raw Interrupts
const INTE = 0x18; // Interrupt Enable
const INTF = 0x1c; // Interrupt Force
const INTS = 0x20; // Interrupt status after masking & forcing
// CS bits
const CS_RROBIN_MASK = 0x1f;
const CS_RROBIN_SHIFT = 16;
const CS_AINSEL_MASK = 0x7;
const CS_AINSEL_SHIFT = 12;
const CS_ERR_STICKY = 1 << 10;
const CS_ERR = 1 << 9;
const CS_READY = 1 << 8;
const CS_START_MANY = 1 << 3;
const CS_START_ONE = 1 << 2;
const CS_TS_EN = 1 << 1;
const CS_EN = 1 << 0;
const CS_WRITE_MASK = (CS_RROBIN_MASK << CS_RROBIN_SHIFT) |
(CS_AINSEL_MASK << CS_AINSEL_SHIFT) |
CS_START_MANY |
CS_START_ONE |
CS_TS_EN |
CS_EN;
// FCS bits
const FCS_THRES_MASK = 0xf;
const FCS_THRESH_SHIFT = 24;
const FCS_LEVEL_MASK = 0xf;
const FCS_LEVEL_SHIFT = 16;
const FCS_OVER = 1 << 11;
const FCS_UNDER = 1 << 10;
const FCS_FULL = 1 << 9;
const FCS_EMPTY = 1 << 8;
const FCS_DREQ_EN = 1 << 3;
const FCS_ERR = 1 << 2;
const FCS_SHIFT = 1 << 1;
const FCS_EN = 1 << 0;
const FCS_WRITE_MASK = (FCS_THRES_MASK << FCS_THRESH_SHIFT) | FCS_DREQ_EN | FCS_ERR | FCS_SHIFT | FCS_EN;
// FIFO_REG bits
const FIFO_ERR = 1 << 15;
// DIV bits
const DIV_INT_MASK = 0xffff;
const DIV_INT_SHIFT = 8;
const DIV_FRAC_MASK = 0xff;
const DIV_FRAC_SHIFT = 0;
// Interrupt bits
const FIFO_INT = 1 << 0;
export class RPADC extends BasePeripheral {
get temperatueEnable() {
return this.cs & CS_TS_EN;
}
get enabled() {
return this.cs & CS_EN;
}
get divider() {
return (1 +
((this.clockDiv >> DIV_INT_SHIFT) & DIV_INT_MASK) +
((this.clockDiv >> DIV_FRAC_SHIFT) & DIV_FRAC_MASK) / 256);
}
get intRaw() {
const thres = (this.fcs >> FCS_THRESH_SHIFT) & FCS_THRES_MASK;
return this.fifo.itemCount >= thres ? FIFO_INT : 0;
}
get intStatus() {
return (this.intRaw & this.intEnable) | this.intForce;
}
get activeChannel() {
return (this.cs >> CS_AINSEL_SHIFT) & CS_AINSEL_MASK;
}
set activeChannel(channel) {
this.cs &= ~(CS_AINSEL_MASK << CS_AINSEL_SHIFT);
this.cs |= (channel & CS_AINSEL_SHIFT) << CS_AINSEL_SHIFT;
}
constructor(rp2040, name) {
super(rp2040, name);
/* Number of ADC channels */
this.numChannels = 5;
/** ADC resolution (in bits) */
this.resolution = 12;
/** Time to read a single sample, in microseconds */
this.sampleTime = 2;
/**
* ADC Channel values. Channels 0...3 are connected to GPIO 26...29, and channel 4 is connected to the built-in
* temperature sensor: T=27-(ADC_voltage-0.706)/0.001721.
*
* Changing the values will change the ADC reading, unless you override onADCRead() with a custom implementation.
*/
this.channelValues = [0, 0, 0, 0, 0];
/**
* Invoked whenever the emulated code performs an ADC read.
*
* The default implementation reads the result from the `channelValues` array, and then calls
* completeADCRead() after `sampleTime` microseconds.
*
* If you override the default implementation, make sure to call `completeADCRead()` after
* `sampleTime` microseconds (or else the ADC read will never complete).
*/
this.onADCRead = (channel) => {
// Default implementation
this.currentChannel = channel;
this.sampleAlarm.schedule(this.sampleTime * 1000);
};
this.fifo = new FIFO(4);
this.dreq = DREQChannel.DREQ_ADC;
// Registers
this.cs = 0;
this.fcs = 0;
this.clockDiv = 0;
this.intEnable = 0;
this.intForce = 0;
this.result = 0;
// Status
this.busy = false;
this.err = false;
this.currentChannel = 0;
this.sampleAlarm = this.rp2040.clock.createAlarm(() => this.completeADCRead(this.channelValues[this.currentChannel], false));
this.multiShotAlarm = this.rp2040.clock.createAlarm(() => {
if (this.cs & CS_START_MANY) {
this.startADCRead();
}
});
}
checkInterrupts() {
this.rp2040.setInterrupt(IRQ.ADC_FIFO, !!this.intStatus);
}
startADCRead() {
this.busy = true;
this.onADCRead(this.activeChannel);
}
updateDMA() {
if (this.fcs & FCS_DREQ_EN) {
const thres = (this.fcs >> FCS_THRESH_SHIFT) & FCS_THRES_MASK;
if (this.fifo.itemCount >= thres) {
this.rp2040.dma.setDREQ(this.dreq);
}
else {
this.rp2040.dma.clearDREQ(this.dreq);
}
}
}
completeADCRead(value, error) {
this.busy = false;
this.result = value;
if (error) {
this.cs |= CS_ERR_STICKY | CS_ERR;
}
else {
this.cs &= ~CS_ERR;
}
// FIFO
if (this.fcs & FCS_EN) {
if (this.fifo.full) {
this.fcs |= FCS_OVER;
}
else {
value &= 0xfff; // 12 bits
if (this.fcs & FCS_SHIFT) {
value >>= 4;
}
if (error && this.fcs & FCS_ERR) {
value |= FIFO_ERR;
}
this.fifo.push(value);
this.updateDMA();
this.checkInterrupts();
}
}
// Round-robin
const round = (this.cs >> CS_RROBIN_SHIFT) & CS_RROBIN_MASK;
if (round) {
let channel = this.activeChannel + 1;
while (!(round & (1 << channel))) {
channel = (channel + 1) % this.numChannels;
}
this.activeChannel = channel;
}
// Multi-shot conversions
if (this.cs & CS_START_MANY) {
const clockMHZ = 48;
const sampleTicks = clockMHZ * this.sampleTime;
if (this.divider > sampleTicks) {
// clock runs at 48MHz, subtract 2uS
const micros = (this.divider - sampleTicks) / clockMHZ;
this.multiShotAlarm.schedule(micros * 1000);
}
else {
this.startADCRead();
}
}
}
readUint32(offset) {
switch (offset) {
case CS:
return this.cs | (this.err ? CS_ERR : 0) | (this.busy ? 0 : CS_READY);
case RESULT:
return this.result;
case FCS:
return (this.fcs |
((this.fifo.itemCount & FCS_LEVEL_MASK) << FCS_LEVEL_SHIFT) |
(this.fifo.full ? FCS_FULL : 0) |
(this.fifo.empty ? FCS_EMPTY : 0));
case FIFO_REG:
if (this.fifo.empty) {
this.fcs |= FCS_UNDER;
return 0;
}
else {
const value = this.fifo.pull();
this.updateDMA();
return value;
}
case DIV:
return this.clockDiv;
case INTR:
return this.intRaw;
case INTE:
return this.intEnable;
case INTF:
return this.intForce;
case INTS:
return this.intStatus;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
switch (offset) {
case CS:
this.fcs &= ~(value & CS_ERR_STICKY); // Write-clear bits
this.cs = (this.cs & ~CS_WRITE_MASK) | (value & CS_WRITE_MASK);
if (value & CS_EN && !this.busy && (value & CS_START_ONE || value & CS_START_MANY)) {
this.startADCRead();
}
break;
case FCS:
this.fcs &= ~(value & (FCS_OVER | FCS_UNDER)); // Write-clear bits
this.fcs = (this.fcs & ~FCS_WRITE_MASK) | (value & FCS_WRITE_MASK);
this.checkInterrupts();
break;
case DIV:
this.clockDiv = value;
break;
case INTE:
this.intEnable = value & FIFO_INT;
this.checkInterrupts();
break;
case INTF:
this.intForce = value & FIFO_INT;
this.checkInterrupts();
break;
default:
super.writeUint32(offset, value);
}
}
}
+80
View File
@@ -0,0 +1,80 @@
import { BasePeripheral } from './peripheral.js';
/** Bus priority acknowledge */
const BUS_PRIORITY_ACK = 0x004;
/** Bus fabric performance counter 0 */
const PERFCTR0 = 0x008;
/** Bus fabric performance event select for PERFCTR0 */
const PERFSEL0 = 0x00c;
/** Bus fabric performance counter 1 */
const PERFCTR1 = 0x010;
/** Bus fabric performance event select for PERFCTR1 */
const PERFSEL1 = 0x014;
/** Bus fabric performance counter 2 */
const PERFCTR2 = 0x018;
/** Bus fabric performance event select for PERFCTR2 */
const PERFSEL2 = 0x01c;
/** Bus fabric performance counter 3 */
const PERFCTR3 = 0x020;
/** Bus fabric performance event select for PERFCTR3 */
const PERFSEL3 = 0x024;
export class RPBUSCTRL extends BasePeripheral {
constructor(rp2040, name) {
super(rp2040, name);
this.voltageSelect = 0;
this.perfCtr = [0, 0, 0, 0];
this.perfSel = [0x1f, 0x1f, 0x1f, 0x1f];
}
readUint32(offset) {
switch (offset) {
case BUS_PRIORITY_ACK:
return 1;
case PERFCTR0:
return this.perfCtr[0];
case PERFSEL0:
return this.perfSel[0];
case PERFCTR1:
return this.perfCtr[1];
case PERFSEL1:
return this.perfSel[1];
case PERFCTR2:
return this.perfCtr[2];
case PERFSEL2:
return this.perfSel[2];
case PERFCTR3:
return this.perfCtr[3];
case PERFSEL3:
return this.perfSel[3];
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
switch (offset) {
case PERFCTR0:
this.perfCtr[0] = 0;
break;
case PERFSEL0:
this.perfSel[0] = value & 0x1f;
break;
case PERFCTR1:
this.perfCtr[1] = 0;
break;
case PERFSEL1:
this.perfSel[1] = value & 0x1f;
break;
case PERFCTR2:
this.perfCtr[2] = 0;
break;
case PERFSEL2:
this.perfSel[2] = value & 0x1f;
break;
case PERFCTR3:
this.perfCtr[3] = 0;
break;
case PERFSEL3:
this.perfSel[3] = value & 0x1f;
break;
default:
super.writeUint32(offset, value);
}
}
}
+196
View File
@@ -0,0 +1,196 @@
import { BasePeripheral } from './peripheral.js';
const CLK_GPOUT0_CTRL = 0x00;
const CLK_GPOUT0_DIV = 0x04;
const CLK_GPOUT0_SELECTED = 0x8;
const CLK_GPOUT1_CTRL = 0x0c;
const CLK_GPOUT1_DIV = 0x10;
const CLK_GPOUT1_SELECTED = 0x14;
const CLK_GPOUT2_CTRL = 0x18;
const CLK_GPOUT2_DIV = 0x01c;
const CLK_GPOUT2_SELECTED = 0x20;
const CLK_GPOUT3_CTRL = 0x24;
const CLK_GPOUT3_DIV = 0x28;
const CLK_GPOUT3_SELECTED = 0x2c;
const CLK_REF_CTRL = 0x30;
const CLK_REF_DIV = 0x34;
const CLK_REF_SELECTED = 0x38;
const CLK_SYS_CTRL = 0x3c;
const CLK_SYS_DIV = 0x40;
const CLK_SYS_SELECTED = 0x44;
const CLK_PERI_CTRL = 0x48;
const CLK_PERI_DIV = 0x4c;
const CLK_PERI_SELECTED = 0x50;
const CLK_USB_CTRL = 0x54;
const CLK_USB_DIV = 0x58;
const CLK_USB_SELECTED = 0x5c;
const CLK_ADC_CTRL = 0x60;
const CLK_ADC_DIV = 0x64;
const CLK_ADC_SELECTED = 0x68;
const CLK_RTC_CTRL = 0x6c;
const CLK_RTC_DIV = 0x70;
const CLK_RTC_SELECTED = 0x74;
const CLK_SYS_RESUS_CTRL = 0x78;
const CLK_SYS_RESUS_STATUS = 0x7c;
export class RPClocks extends BasePeripheral {
constructor(rp2040, name) {
super(rp2040, name);
this.gpout0Ctrl = 0;
this.gpout0Div = 0x100;
this.gpout1Ctrl = 0;
this.gpout1Div = 0x100;
this.gpout2Ctrl = 0;
this.gpout2Div = 0x100;
this.gpout3Ctrl = 0;
this.gpout3Div = 0x100;
this.refCtrl = 0;
this.refDiv = 0x100;
this.periCtrl = 0;
this.periDiv = 0x100;
this.usbCtrl = 0;
this.usbDiv = 0x100;
this.sysCtrl = 0;
this.sysDiv = 0x100;
this.adcCtrl = 0;
this.adcDiv = 0x100;
this.rtcCtrl = 0;
this.rtcDiv = 0x100;
}
readUint32(offset) {
switch (offset) {
case CLK_GPOUT0_CTRL:
return this.gpout0Ctrl & 0b100110001110111100000;
case CLK_GPOUT0_DIV:
return this.gpout0Div;
case CLK_GPOUT0_SELECTED:
return 1;
case CLK_GPOUT1_CTRL:
return this.gpout1Ctrl & 0b100110001110111100000;
case CLK_GPOUT1_DIV:
return this.gpout1Div;
case CLK_GPOUT1_SELECTED:
return 1;
case CLK_GPOUT2_CTRL:
return this.gpout2Ctrl & 0b100110001110111100000;
case CLK_GPOUT2_DIV:
return this.gpout2Div;
case CLK_GPOUT2_SELECTED:
return 1;
case CLK_GPOUT3_CTRL:
return this.gpout3Ctrl & 0b100110001110111100000;
case CLK_GPOUT3_DIV:
return this.gpout3Div;
case CLK_GPOUT3_SELECTED:
return 1;
case CLK_REF_CTRL:
return this.refCtrl & 0b000001100011;
case CLK_REF_DIV:
return this.refDiv & 0x30; // b8..9 = int divisor. no frac divisor present
case CLK_REF_SELECTED:
return 1 << (this.refCtrl & 0x03);
case CLK_SYS_CTRL:
return this.sysCtrl & 0b000011100001;
case CLK_SYS_DIV:
return this.sysDiv;
case CLK_SYS_SELECTED:
return 1 << (this.sysCtrl & 0x01);
case CLK_PERI_CTRL:
return this.periCtrl & 0b110011100000;
case CLK_PERI_DIV:
return this.periDiv;
case CLK_PERI_SELECTED:
return 1;
case CLK_USB_CTRL:
return this.usbCtrl & 0b100110000110011100000;
case CLK_USB_DIV:
return this.usbDiv;
case CLK_USB_SELECTED:
return 1;
case CLK_ADC_CTRL:
return this.adcCtrl & 0b100110000110011100000;
case CLK_ADC_DIV:
return this.adcDiv & 0x30;
case CLK_ADC_SELECTED:
return 1;
case CLK_RTC_CTRL:
return this.rtcCtrl & 0b100110000110011100000;
case CLK_RTC_DIV:
return this.rtcDiv & 0x30;
case CLK_RTC_SELECTED:
return 1;
case CLK_SYS_RESUS_CTRL:
return 0xff;
case CLK_SYS_RESUS_STATUS:
return 0; /* clock resus not implemented */
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
switch (offset) {
case CLK_GPOUT0_CTRL:
this.gpout0Ctrl = value;
break;
case CLK_GPOUT0_DIV:
this.gpout0Div = value;
break;
case CLK_GPOUT1_CTRL:
this.gpout1Ctrl = value;
break;
case CLK_GPOUT1_DIV:
this.gpout1Div = value;
break;
case CLK_GPOUT2_CTRL:
this.gpout2Ctrl = value;
break;
case CLK_GPOUT2_DIV:
this.gpout2Div = value;
break;
case CLK_GPOUT3_CTRL:
this.gpout3Ctrl = value;
break;
case CLK_GPOUT3_DIV:
this.gpout3Div = value;
break;
case CLK_REF_CTRL:
this.refCtrl = value;
break;
case CLK_REF_DIV:
this.refDiv = value;
break;
case CLK_SYS_CTRL:
this.sysCtrl = value;
break;
case CLK_SYS_DIV:
this.sysDiv = value;
break;
case CLK_PERI_CTRL:
this.periCtrl = value;
break;
case CLK_PERI_DIV:
this.periDiv = value;
break;
case CLK_USB_CTRL:
this.usbCtrl = value;
break;
case CLK_USB_DIV:
this.usbDiv = value;
break;
case CLK_ADC_CTRL:
this.adcCtrl = value;
break;
case CLK_ADC_DIV:
this.adcDiv = value;
break;
case CLK_RTC_CTRL:
this.rtcCtrl = value;
break;
case CLK_RTC_DIV:
this.rtcDiv = value;
break;
case CLK_SYS_RESUS_CTRL:
return; /* clock resus not implemented */
default:
super.writeUint32(offset, value);
break;
}
}
}
+506
View File
@@ -0,0 +1,506 @@
import { IRQ } from '../irq.js';
import { BasePeripheral } from './peripheral.js';
export var DREQChannel;
(function (DREQChannel) {
DREQChannel[DREQChannel["DREQ_PIO0_TX0"] = 0] = "DREQ_PIO0_TX0";
DREQChannel[DREQChannel["DREQ_PIO0_TX1"] = 1] = "DREQ_PIO0_TX1";
DREQChannel[DREQChannel["DREQ_PIO0_TX2"] = 2] = "DREQ_PIO0_TX2";
DREQChannel[DREQChannel["DREQ_PIO0_TX3"] = 3] = "DREQ_PIO0_TX3";
DREQChannel[DREQChannel["DREQ_PIO0_RX0"] = 4] = "DREQ_PIO0_RX0";
DREQChannel[DREQChannel["DREQ_PIO0_RX1"] = 5] = "DREQ_PIO0_RX1";
DREQChannel[DREQChannel["DREQ_PIO0_RX2"] = 6] = "DREQ_PIO0_RX2";
DREQChannel[DREQChannel["DREQ_PIO0_RX3"] = 7] = "DREQ_PIO0_RX3";
DREQChannel[DREQChannel["DREQ_PIO1_TX0"] = 8] = "DREQ_PIO1_TX0";
DREQChannel[DREQChannel["DREQ_PIO1_TX1"] = 9] = "DREQ_PIO1_TX1";
DREQChannel[DREQChannel["DREQ_PIO1_TX2"] = 10] = "DREQ_PIO1_TX2";
DREQChannel[DREQChannel["DREQ_PIO1_TX3"] = 11] = "DREQ_PIO1_TX3";
DREQChannel[DREQChannel["DREQ_PIO1_RX0"] = 12] = "DREQ_PIO1_RX0";
DREQChannel[DREQChannel["DREQ_PIO1_RX1"] = 13] = "DREQ_PIO1_RX1";
DREQChannel[DREQChannel["DREQ_PIO1_RX2"] = 14] = "DREQ_PIO1_RX2";
DREQChannel[DREQChannel["DREQ_PIO1_RX3"] = 15] = "DREQ_PIO1_RX3";
DREQChannel[DREQChannel["DREQ_SPI0_TX"] = 16] = "DREQ_SPI0_TX";
DREQChannel[DREQChannel["DREQ_SPI0_RX"] = 17] = "DREQ_SPI0_RX";
DREQChannel[DREQChannel["DREQ_SPI1_TX"] = 18] = "DREQ_SPI1_TX";
DREQChannel[DREQChannel["DREQ_SPI1_RX"] = 19] = "DREQ_SPI1_RX";
DREQChannel[DREQChannel["DREQ_UART0_TX"] = 20] = "DREQ_UART0_TX";
DREQChannel[DREQChannel["DREQ_UART0_RX"] = 21] = "DREQ_UART0_RX";
DREQChannel[DREQChannel["DREQ_UART1_TX"] = 22] = "DREQ_UART1_TX";
DREQChannel[DREQChannel["DREQ_UART1_RX"] = 23] = "DREQ_UART1_RX";
DREQChannel[DREQChannel["DREQ_PWM_WRAP0"] = 24] = "DREQ_PWM_WRAP0";
DREQChannel[DREQChannel["DREQ_PWM_WRAP1"] = 25] = "DREQ_PWM_WRAP1";
DREQChannel[DREQChannel["DREQ_PWM_WRAP2"] = 26] = "DREQ_PWM_WRAP2";
DREQChannel[DREQChannel["DREQ_PWM_WRAP3"] = 27] = "DREQ_PWM_WRAP3";
DREQChannel[DREQChannel["DREQ_PWM_WRAP4"] = 28] = "DREQ_PWM_WRAP4";
DREQChannel[DREQChannel["DREQ_PWM_WRAP5"] = 29] = "DREQ_PWM_WRAP5";
DREQChannel[DREQChannel["DREQ_PWM_WRAP6"] = 30] = "DREQ_PWM_WRAP6";
DREQChannel[DREQChannel["DREQ_PWM_WRAP7"] = 31] = "DREQ_PWM_WRAP7";
DREQChannel[DREQChannel["DREQ_I2C0_TX"] = 32] = "DREQ_I2C0_TX";
DREQChannel[DREQChannel["DREQ_I2C0_RX"] = 33] = "DREQ_I2C0_RX";
DREQChannel[DREQChannel["DREQ_I2C1_TX"] = 34] = "DREQ_I2C1_TX";
DREQChannel[DREQChannel["DREQ_I2C1_RX"] = 35] = "DREQ_I2C1_RX";
DREQChannel[DREQChannel["DREQ_ADC"] = 36] = "DREQ_ADC";
DREQChannel[DREQChannel["DREQ_XIP_STREAM"] = 37] = "DREQ_XIP_STREAM";
DREQChannel[DREQChannel["DREQ_XIP_SSITX"] = 38] = "DREQ_XIP_SSITX";
DREQChannel[DREQChannel["DREQ_XIP_SSIRX"] = 39] = "DREQ_XIP_SSIRX";
DREQChannel[DREQChannel["DREQ_MAX"] = 40] = "DREQ_MAX";
})(DREQChannel || (DREQChannel = {}));
var TREQ;
(function (TREQ) {
TREQ[TREQ["Timer0"] = 59] = "Timer0";
TREQ[TREQ["Timer1"] = 60] = "Timer1";
TREQ[TREQ["Timer2"] = 61] = "Timer2";
TREQ[TREQ["Timer3"] = 62] = "Timer3";
TREQ[TREQ["Permanent"] = 63] = "Permanent";
})(TREQ || (TREQ = {}));
// Per-channel registers
const CHn_READ_ADDR = 0x000; // DMA Channel n Read Address pointer
const CHn_WRITE_ADDR = 0x004; // DMA Channel n Write Address pointer
const CHn_TRANS_COUNT = 0x008; // DMA Channel n Transfer Count
const CHn_CTRL_TRIG = 0x00c; // DMA Channel n Control and Status
const CHn_AL1_CTRL = 0x010; // Alias for channel n CTRL register
const CHn_AL1_READ_ADDR = 0x014; // Alias for channel n READ_ADDR register
const CHn_AL1_WRITE_ADDR = 0x018; // Alias for channel n WRITE_ADDR register
const CHn_AL1_TRANS_COUNT_TRIG = 0x01c; // Alias for channel n TRANS_COUNT register
const CHn_AL2_CTRL = 0x020; // Alias for channel n CTRL register
const CHn_AL2_TRANS_COUNT = 0x024; // Alias for channel n TRANS_COUNT register
const CHn_AL2_READ_ADDR = 0x028; // Alias for channel n READ_ADDR register
const CHn_AL2_WRITE_ADDR_TRIG = 0x02c; // Alias for channel n WRITE_ADDR register
const CHn_AL3_CTRL = 0x030; // Alias for channel n CTRL register
const CHn_AL3_WRITE_ADDR = 0x034; // Alias for channel n WRITE_ADDR register
const CHn_AL3_TRANS_COUNT = 0x038; // Alias for channel n TRANS_COUNT register
const CHn_AL3_READ_ADDR_TRIG = 0x03c; // Alias for channel n READ_ADDR register
const CHn_DBG_CTDREQ = 0x800;
const CHn_DBG_TCR = 0x804;
const CHANNEL_REGISTERS_SIZE = 12 * 0x40;
const CHANNEL_REGISTERS_MASK = 0x83f;
// General DMA registers
const INTR = 0x400; // Interrupt Status (raw)
const INTE0 = 0x404; // Interrupt Enables for IRQ 0
const INTF0 = 0x408; // Force Interrupts
const INTS0 = 0x40c; // Interrupt Status for IRQ 0
const INTE1 = 0x414; // Interrupt Enables for IRQ 1
const INTF1 = 0x418; // Force Interrupts for IRQ 1
const INTS1 = 0x41c; // Interrupt Status (masked) for IRQ 1
const TIMER0 = 0x420; // Pacing (X/Y) Fractional Timer
const TIMER1 = 0x424; // Pacing (X/Y) Fractional Timer
const TIMER2 = 0x428; // Pacing (X/Y) Fractional Timer
const TIMER3 = 0x42c; // Pacing (X/Y) Fractional Timer
const MULTI_CHAN_TRIGGER = 0x430; // Trigger one or more channels simultaneously
const SNIFF_CTRL = 0x434; // Sniffer Control
const SNIFF_DATA = 0x438; // Data accumulator for sniff hardware
const FIFO_LEVELS = 0x440; // Debug RAF, WAF, TDF levels
const CHAN_ABORT = 0x444; // Abort an in-progress transfer sequence on one or more channels
const N_CHANNELS = 0x448;
// CHn_CTRL_TRIG bits
const AHB_ERROR = 1 << 31;
const READ_ERROR = 1 << 30;
const WRITE_ERROR = 1 << 29;
const BUSY = 1 << 24;
const SNIFF_EN = 1 << 23;
const BSWAP = 1 << 22;
const IRQ_QUIET = 1 << 21;
const TREQ_SEL_MASK = 0x3f;
const TREQ_SEL_SHIFT = 15;
const CHAIN_TO_MASK = 0xf;
const CHAIN_TO_SHIFT = 11;
const RING_SEL = 1 << 10;
const RING_SIZE_MASK = 0xf;
const RING_SIZE_SHIFT = 6;
const INCR_WRITE = 1 << 5;
const INCR_READ = 1 << 4;
const DATA_SIZE_MASK = 0x3;
const DATA_SIZE_SHIFT = 2;
const HIGH_PRIORITY = 1 << 1;
const EN = 1 << 0;
const CHn_CTRL_TRIG_WRITE_MASK = 0xffffff;
const CHn_CTRL_TRIG_WC_MASK = READ_ERROR | WRITE_ERROR;
export class RPDMAChannel {
constructor(dma, rp2040, index) {
this.dma = dma;
this.rp2040 = rp2040;
this.index = index;
this.ctrl = 0;
this.readAddr = 0;
this.writeAddr = 0;
this.transCount = 0;
this.dreqCounter = 0;
this.transCountReload = 0;
this.treqValue = 0;
this.dataSize = 1;
this.chainTo = 0;
this.ringMask = 0;
this.transferFn = () => 0;
this.transfer8 = () => {
const { rp2040 } = this;
rp2040.writeUint8(this.writeAddr, rp2040.readUint8(this.readAddr));
};
this.transfer16 = () => {
const { rp2040 } = this;
rp2040.writeUint16(this.writeAddr, rp2040.readUint16(this.readAddr));
};
this.transferSwap16 = () => {
const { rp2040 } = this;
const input = rp2040.readUint16(this.readAddr);
rp2040.writeUint16(this.writeAddr, ((input & 0xff) << 8) | (input >> 8));
};
this.transfer32 = () => {
const { rp2040 } = this;
rp2040.writeUint32(this.writeAddr, rp2040.readUint32(this.readAddr));
};
this.transferSwap32 = () => {
const { rp2040 } = this;
const input = rp2040.readUint32(this.readAddr);
rp2040.writeUint32(this.writeAddr, ((input & 0x000000ff) << 24) |
((input & 0x0000ff00) << 8) |
((input & 0x00ff0000) >> 8) |
((input >> 24) & 0xff));
};
this.transfer = () => {
var _a;
const { ctrl, dataSize, ringMask } = this;
this.transferFn();
if (ctrl & INCR_READ) {
if (ringMask && !(ctrl & RING_SEL)) {
this.readAddr = (this.readAddr & ~ringMask) | ((this.readAddr + dataSize) & ringMask);
}
else {
this.readAddr += dataSize;
}
}
if (ctrl & INCR_WRITE) {
if (ringMask && ctrl & RING_SEL) {
this.writeAddr = (this.writeAddr & ~ringMask) | ((this.writeAddr + dataSize) & ringMask);
}
else {
this.writeAddr += dataSize;
}
}
this.transCount--;
if (this.transCount > 0) {
this.scheduleTransfer();
}
else {
this.ctrl &= ~BUSY;
if (!(this.ctrl & IRQ_QUIET)) {
this.dma.intRaw |= 1 << this.index;
this.dma.checkInterrupts();
}
if (this.chainTo !== this.index) {
(_a = this.dma.channels[this.chainTo]) === null || _a === void 0 ? void 0 : _a.start();
}
}
};
this.transferAlarm = rp2040.clock.createAlarm(this.transfer);
this.reset();
}
start() {
if (!(this.ctrl & EN) || this.ctrl & BUSY) {
return;
}
this.ctrl |= BUSY;
this.transCount = this.transCountReload;
if (this.transCount) {
this.scheduleTransfer();
}
}
get treq() {
return this.treqValue;
}
get active() {
return this.ctrl & EN && this.ctrl & BUSY;
}
scheduleTransfer() {
if (this.dma.dreq[this.treqValue] || this.treqValue === TREQ.Permanent) {
this.transferAlarm.schedule(0);
}
else {
const delay = this.dma.getTimer(this.treqValue);
if (delay) {
this.transferAlarm.schedule(delay * 1000);
}
}
}
abort() {
this.ctrl &= ~BUSY;
this.transferAlarm.cancel();
}
readUint32(offset) {
switch (offset) {
case CHn_READ_ADDR:
case CHn_AL1_READ_ADDR:
case CHn_AL2_READ_ADDR:
case CHn_AL3_READ_ADDR_TRIG:
return this.readAddr;
case CHn_WRITE_ADDR:
case CHn_AL1_WRITE_ADDR:
case CHn_AL2_WRITE_ADDR_TRIG:
case CHn_AL3_WRITE_ADDR:
return this.writeAddr;
case CHn_TRANS_COUNT:
case CHn_AL1_TRANS_COUNT_TRIG:
case CHn_AL2_TRANS_COUNT:
case CHn_AL3_TRANS_COUNT:
return this.transCount;
case CHn_CTRL_TRIG:
case CHn_AL1_CTRL:
case CHn_AL2_CTRL:
case CHn_AL3_CTRL:
return this.ctrl;
case CHn_DBG_CTDREQ:
return this.dreqCounter;
case CHn_DBG_TCR:
return this.transCountReload;
}
return 0;
}
writeUint32(offset, value) {
switch (offset) {
case CHn_READ_ADDR:
case CHn_AL1_READ_ADDR:
case CHn_AL2_READ_ADDR:
case CHn_AL3_READ_ADDR_TRIG:
this.readAddr = value;
break;
case CHn_WRITE_ADDR:
case CHn_AL1_WRITE_ADDR:
case CHn_AL2_WRITE_ADDR_TRIG:
case CHn_AL3_WRITE_ADDR:
this.writeAddr = value;
break;
case CHn_TRANS_COUNT:
case CHn_AL1_TRANS_COUNT_TRIG:
case CHn_AL2_TRANS_COUNT:
case CHn_AL3_TRANS_COUNT:
this.transCountReload = value;
break;
case CHn_CTRL_TRIG:
case CHn_AL1_CTRL:
case CHn_AL2_CTRL:
case CHn_AL3_CTRL: {
this.ctrl = (this.ctrl & ~CHn_CTRL_TRIG_WRITE_MASK) | (value & CHn_CTRL_TRIG_WRITE_MASK);
this.ctrl &= ~(value & CHn_CTRL_TRIG_WC_MASK); // Handle write-clear (WC) bits
this.treqValue = (this.ctrl >> TREQ_SEL_SHIFT) & TREQ_SEL_MASK;
this.chainTo = (this.ctrl >> CHAIN_TO_SHIFT) & CHAIN_TO_MASK;
const ringSize = (this.ctrl >> RING_SIZE_SHIFT) & RING_SIZE_MASK;
this.ringMask = ringSize ? (1 << ringSize) - 1 : 0;
switch ((this.ctrl >> DATA_SIZE_SHIFT) & DATA_SIZE_MASK) {
case 1:
this.dataSize = 2;
this.transferFn = this.ctrl & BSWAP ? this.transferSwap16 : this.transfer16;
break;
case 2:
this.dataSize = 4;
this.transferFn = this.ctrl & BSWAP ? this.transferSwap32 : this.transfer32;
break;
case 0:
default:
this.transferFn = this.transfer8;
this.dataSize = 1;
}
if (this.ctrl & EN && this.ctrl & BUSY) {
this.scheduleTransfer();
}
if (!(this.ctrl & EN)) {
this.transferAlarm.cancel();
}
break;
}
case CHn_DBG_CTDREQ:
this.dreqCounter = 0;
break;
}
if (offset === CHn_AL3_READ_ADDR_TRIG ||
offset === CHn_AL2_WRITE_ADDR_TRIG ||
offset === CHn_AL1_TRANS_COUNT_TRIG ||
offset === CHn_CTRL_TRIG) {
if (value) {
this.start();
}
else if (this.ctrl & IRQ_QUIET) {
// Null trigger interrupts
this.dma.intRaw |= 1 << this.index;
this.dma.checkInterrupts();
}
}
}
reset() {
this.writeUint32(CHn_CTRL_TRIG, this.index << CHAIN_TO_SHIFT);
}
}
export class RPDMA extends BasePeripheral {
constructor() {
super(...arguments);
this.channels = [
new RPDMAChannel(this, this.rp2040, 0),
new RPDMAChannel(this, this.rp2040, 1),
new RPDMAChannel(this, this.rp2040, 2),
new RPDMAChannel(this, this.rp2040, 3),
new RPDMAChannel(this, this.rp2040, 4),
new RPDMAChannel(this, this.rp2040, 5),
new RPDMAChannel(this, this.rp2040, 6),
new RPDMAChannel(this, this.rp2040, 7),
new RPDMAChannel(this, this.rp2040, 8),
new RPDMAChannel(this, this.rp2040, 9),
new RPDMAChannel(this, this.rp2040, 10),
new RPDMAChannel(this, this.rp2040, 11),
];
this.intRaw = 0;
this.intEnable0 = 0;
this.intForce0 = 0;
this.intEnable1 = 0;
this.intForce1 = 0;
this.timer0 = 0;
this.timer1 = 0;
this.timer2 = 0;
this.timer3 = 0;
this.dreq = Array(DREQChannel.DREQ_MAX);
}
get intStatus0() {
return (this.intRaw & this.intEnable0) | this.intForce0;
}
get intStatus1() {
return (this.intRaw & this.intEnable1) | this.intForce1;
}
readUint32(offset) {
if ((offset & 0x7ff) < CHANNEL_REGISTERS_SIZE) {
const channelIndex = (offset & 0x7ff) >> 6;
return this.channels[channelIndex].readUint32(offset & CHANNEL_REGISTERS_MASK);
}
switch (offset) {
case TIMER0:
return this.timer0;
case TIMER1:
return this.timer1;
case TIMER2:
return this.timer2;
case TIMER3:
return this.timer3;
case INTR:
return this.intRaw;
case INTE0:
return this.intEnable0;
case INTF0:
return this.intForce0;
case INTS0:
return this.intStatus0;
case INTE1:
return this.intEnable1;
case INTF1:
return this.intForce1;
case INTS1:
return this.intStatus1;
case N_CHANNELS:
return this.channels.length;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
if ((offset & 0x7ff) < CHANNEL_REGISTERS_SIZE) {
const channelIndex = (offset & 0x7ff) >> 6;
this.channels[channelIndex].writeUint32(offset & CHANNEL_REGISTERS_MASK, value);
return;
}
switch (offset) {
case TIMER0:
this.timer0 = value;
return;
case TIMER1:
this.timer1 = value;
return;
case TIMER2:
this.timer2 = value;
return;
case TIMER3:
this.timer3 = value;
return;
case INTR:
case INTS0:
case INTS1:
this.intRaw &= ~this.rawWriteValue;
this.checkInterrupts();
return;
case INTE0:
this.intEnable0 = value & 0xffff;
this.checkInterrupts();
return;
case INTF0:
this.intForce0 = value & 0xffff;
this.checkInterrupts();
return;
case INTE1:
this.intEnable1 = value & 0xffff;
this.checkInterrupts();
return;
case INTF1:
this.intForce1 = value & 0xffff;
this.checkInterrupts();
return;
case MULTI_CHAN_TRIGGER:
for (const chan of this.channels) {
if (value & (1 << chan.index)) {
chan.start();
}
}
return;
case CHAN_ABORT:
for (const chan of this.channels) {
if (value & (1 << chan.index)) {
chan.abort();
}
}
return;
default:
super.writeUint32(offset, value);
}
}
setDREQ(dreqChannel) {
const { dreq } = this;
if (!dreq[dreqChannel]) {
dreq[dreqChannel] = true;
for (const channel of this.channels) {
if (channel.treq === dreqChannel && channel.active) {
channel.scheduleTransfer();
}
}
}
}
clearDREQ(dreqChannel) {
this.dreq[dreqChannel] = false;
}
/**
* Returns the number of microseconds for a cycle of the given DMA timer, or 0 if the timer is disabled.
*/
getTimer(treq) {
let dividend = 0, divisor = 1;
switch (treq) {
case TREQ.Permanent:
dividend = 1;
divisor = 1;
break;
case TREQ.Timer0:
dividend = this.timer0 >>> 16;
divisor = this.timer0 & 0xffff;
break;
case TREQ.Timer1:
dividend = this.timer1 >>> 16;
divisor = this.timer1 & 0xffff;
break;
case TREQ.Timer2:
dividend = this.timer2 >>> 16;
divisor = this.timer2 & 0xffff;
break;
case TREQ.Timer3:
dividend = this.timer3 >>> 36;
divisor = this.timer3 & 0xffff;
break;
}
if (divisor === 0) {
return 0;
}
return ((dividend / divisor) * 1e6) / this.rp2040.clkSys;
}
checkInterrupts() {
this.rp2040.setInterrupt(IRQ.DMA_IRQ0, !!this.intStatus0);
this.rp2040.setInterrupt(IRQ.DMA_IRQ1, !!this.intStatus1);
}
}
+504
View File
@@ -0,0 +1,504 @@
import { FIFO } from '../utils/fifo.js';
import { BasePeripheral } from './peripheral.js';
const IC_CON = 0x00; // I2C Control Register
const IC_TAR = 0x04; // I2C Target Address Register
const IC_SAR = 0x08; // I2C Slave Address Register
const IC_DATA_CMD = 0x10; // I2C Rx/Tx Data Buffer and Command Register
const IC_SS_SCL_HCNT = 0x14; // Standard Speed I2C Clock SCL High Count Register
const IC_SS_SCL_LCNT = 0x18; // Standard Speed I2C Clock SCL Low Count Register
const IC_FS_SCL_HCNT = 0x1c; // Fast Mode or Fast Mode Plus I2C Clock SCL High Count Register
const IC_FS_SCL_LCNT = 0x20; // Fast Mode or Fast Mode Plus I2C Clock SCL Low Count Register
const IC_INTR_STAT = 0x2c; // I2C Interrupt Status Register
const IC_INTR_MASK = 0x30; // I2C Interrupt Mask Register
const IC_RAW_INTR_STAT = 0x34; // I2C Raw Interrupt Status Register
const IC_RX_TL = 0x38; // I2C Receive FIFO Threshold Register
const IC_TX_TL = 0x3c; // I2C Transmit FIFO Threshold Register
const IC_CLR_INTR = 0x40; // Clear Combined and Individual Interrupt Register
const IC_CLR_RX_UNDER = 0x44; // Clear RX_UNDER Interrupt Register
const IC_CLR_RX_OVER = 0x48; // Clear RX_OVER Interrupt Register
const IC_CLR_TX_OVER = 0x4c; // Clear TX_OVER Interrupt Register
const IC_CLR_RD_REQ = 0x50; // Clear RD_REQ Interrupt Register
const IC_CLR_TX_ABRT = 0x54; // Clear TX_ABRT Interrupt Register
const IC_CLR_RX_DONE = 0x58; // Clear RX_DONE Interrupt Register
const IC_CLR_ACTIVITY = 0x5c; // Clear ACTIVITY Interrupt Register
const IC_CLR_STOP_DET = 0x60; // Clear STOP_DET Interrupt Register
const IC_CLR_START_DET = 0x64; // Clear START_DET Interrupt Register
const IC_CLR_GEN_CALL = 0x68; // Clear GEN_CALL Interrupt Register
const IC_ENABLE = 0x6c; // I2C ENABLE Register
const IC_STATUS = 0x70; // I2C STATUS Register
const IC_TXFLR = 0x74; // I2C Transmit FIFO Level Register
const IC_RXFLR = 0x78; // I2C Receive FIFO Level Register
const IC_SDA_HOLD = 0x7c; // I2C SDA Hold Time Length Register
const IC_TX_ABRT_SOURCE = 0x80; // I2C Transmit Abort Source Register
const IC_SLV_DATA_NACK_ONLY = 0x84; // Generate Slave Data NACK Register
const IC_DMA_CR = 0x88; // DMA Control Register
const IC_DMA_TDLR = 0x8c; // DMA Transmit Data Level Register
const IC_DMA_RDLR = 0x90; // DMA Transmit Data Level Register
const IC_SDA_SETUP = 0x94; // I2C SDA Setup Register
const IC_ACK_GENERAL_CALL = 0x98; // I2C ACK General Call Register
const IC_ENABLE_STATUS = 0x9c; // I2C Enable Status Register
const IC_FS_SPKLEN = 0xa0; // I2C SS, FS or FM+ spike suppression limit
const IC_CLR_RESTART_DET = 0xa8; // Clear RESTART_DET Interrupt Register
const IC_COMP_PARAM_1 = 0xf4; // Component Parameter Register 1
const IC_COMP_VERSION = 0xf8; // I2C Component Version Register
const IC_COMP_TYPE = 0xfc; // I2C Component Type Register
// IC_CON bits:
const STOP_DET_IF_MASTER_ACTIVE = 1 << 10;
const RX_FIFO_FULL_HLD_CTRL = 1 << 9;
const TX_EMPTY_CTRL = 1 << 8;
const STOP_DET_IFADDRESSED = 1 << 7;
const IC_SLAVE_DISABLE = 1 << 6;
const IC_RESTART_EN = 1 << 5;
const IC_10BITADDR_MASTER = 1 << 4;
const IC_10BITADDR_SLAVE = 1 << 3;
const SPEED_SHIFT = 1;
const SPEED_MASK = 0x3;
const MASTER_MODE = 1 << 0;
// IC_TAR bits:
const SPECIAL = 1 << 11;
const GC_OR_START = 1 << 10;
// IC_STATUS bits:
const SLV_ACTIVITY = 1 << 6;
const MST_ACTIVITY = 1 << 5;
const RFF = 1 << 4;
const RFNE = 1 << 3;
const TFE = 1 << 2;
const TFNF = 1 << 1;
const ACTIVITY = 1 << 0;
// IC_ENABLE bits:
const TX_CMD_BLOCK = 1 << 2;
const ABORT = 1 << 1;
const ENABLE = 1 << 0;
// IC_TX_ABRT_SOURCE bits:
const TX_FLUSH_CNT_MASK = 0x1ff;
const TX_FLUSH_CNT_SHIFT = 23;
const ABRT_USER_ABRT = 1 << 16;
const ABRT_SLVRD_INT = 1 << 15;
const ABRT_SLV_ARBLOST = 1 << 14;
const ABRT_SLVFLUSH_TXFIFO = 1 << 13;
const ARB_LOST = 1 << 12;
const ABRT_MASTER_DIS = 1 << 11;
const ABRT_10B_RD_NORSTRT = 1 << 10;
const ABRT_SBYTE_NORSTRT = 1 << 9;
const ABRT_HS_NORSTRT = 1 << 8;
const ABRT_SBYTE_ACKDET = 1 << 7;
const ABRT_HS_ACKDET = 1 << 6;
const ABRT_GCALL_READ = 1 << 5;
const ABRT_GCALL_NOACK = 1 << 4;
const ABRT_TXDATA_NOACK = 1 << 3;
const ABRT_10ADDR2_NOACK = 1 << 2;
const ABRT_10ADDR1_NOACK = 1 << 1;
const ABRT_7B_ADDR_NOACK = 1 << 0;
/* Connection parameters */
export var I2CMode;
(function (I2CMode) {
I2CMode[I2CMode["Write"] = 0] = "Write";
I2CMode[I2CMode["Read"] = 1] = "Read";
})(I2CMode || (I2CMode = {}));
export var I2CSpeed;
(function (I2CSpeed) {
I2CSpeed[I2CSpeed["Invalid"] = 0] = "Invalid";
/* standard mode (100 kbit/s) */
I2CSpeed[I2CSpeed["Standard"] = 1] = "Standard";
/* fast mode (<=400 kbit/s) or fast mode plus (<=1000Kbit/s) */
I2CSpeed[I2CSpeed["FastMode"] = 2] = "FastMode";
/* high speed mode (3.4 Mbit/s) */
I2CSpeed[I2CSpeed["HighSpeedMode"] = 3] = "HighSpeedMode";
})(I2CSpeed || (I2CSpeed = {}));
var I2CState;
(function (I2CState) {
I2CState[I2CState["Idle"] = 0] = "Idle";
I2CState[I2CState["Start"] = 1] = "Start";
I2CState[I2CState["Connect"] = 2] = "Connect";
I2CState[I2CState["Connected"] = 3] = "Connected";
I2CState[I2CState["Stop"] = 4] = "Stop";
})(I2CState || (I2CState = {}));
// Interrupts
const R_RESTART_DET = 1 << 12; // Slave mode only
const R_GEN_CALL = 1 << 11;
const R_START_DET = 1 << 10;
const R_STOP_DET = 1 << 9;
const R_ACTIVITY = 1 << 8;
const R_RX_DONE = 1 << 7;
const R_TX_ABRT = 1 << 6;
const R_RD_REQ = 1 << 5;
const R_TX_EMPTY = 1 << 4;
const R_TX_OVER = 1 << 3;
const R_RX_FULL = 1 << 2;
const R_RX_OVER = 1 << 1;
const R_RX_UNDER = 1 << 0;
// FIFO entry bits
const FIRST_DATA_BYTE = 1 << 10;
const RESTART = 1 << 10;
const STOP = 1 << 9;
const CMD = 1 << 8; // 0 for write, 1 for read
export class RPI2C extends BasePeripheral {
get intStatus() {
return this.intRaw & this.intEnable;
}
get speed() {
return ((this.control >> SPEED_SHIFT) & SPEED_MASK);
}
get sclLowPeriod() {
return this.speed === I2CSpeed.Standard ? this.ssClockLowPeriod : this.fsClockLowPeriod;
}
get sclHighPeriod() {
return this.speed === I2CSpeed.Standard ? this.ssClockHighPeriod : this.fsClockHighPeriod;
}
get masterBits() {
return this.control & IC_10BITADDR_MASTER ? 10 : 7;
}
constructor(rp2040, name, irq) {
super(rp2040, name);
this.irq = irq;
this.state = I2CState.Idle;
this.busy = false;
this.stop = false;
this.pendingRestart = false;
this.firstByte = false;
this.rxFIFO = new FIFO(16);
this.txFIFO = new FIFO(16);
// user provided callbacks
this.onStart = () => this.completeStart();
this.onConnect = () => this.completeConnect(false);
this.onWriteByte = () => this.completeWrite(false);
this.onReadByte = () => this.completeRead(0xff);
this.onStop = () => this.completeStop();
this.enable = 0;
this.rxThreshold = 0;
this.txThreshold = 0;
this.control = IC_SLAVE_DISABLE | IC_RESTART_EN | (I2CSpeed.FastMode << SPEED_SHIFT) | MASTER_MODE;
this.ssClockHighPeriod = 0x0028;
this.ssClockLowPeriod = 0x002f;
this.fsClockHighPeriod = 0x0006;
this.fsClockLowPeriod = 0x000d;
this.targetAddress = 0x55;
this.slaveAddress = 0x55;
this.abortSource = 0;
this.intRaw = 0;
this.intEnable = 0;
this.spikelen = 0x07;
}
checkInterrupts() {
this.rp2040.setInterrupt(this.irq, !!this.intStatus);
}
clearInterrupts(mask) {
if (this.intRaw & mask) {
this.intRaw &= ~mask;
this.checkInterrupts();
return 1;
}
else {
return 0;
}
}
setInterrupts(mask) {
if (!(this.intRaw & mask)) {
this.intRaw |= mask;
this.checkInterrupts();
}
}
abort(reason) {
this.abortSource &= ~TX_FLUSH_CNT_MASK;
this.abortSource |= reason | (this.txFIFO.itemCount << TX_FLUSH_CNT_SHIFT);
this.txFIFO.reset();
this.setInterrupts(R_TX_ABRT);
}
nextCommand() {
const enabled = this.enable & ENABLE;
const blocked = this.enable & TX_CMD_BLOCK;
if (this.txFIFO.empty || this.busy || blocked || !enabled) {
return;
}
this.busy = true;
const restart = !!(this.txFIFO.peek() & RESTART) && !this.pendingRestart && !this.stop;
if (this.state === I2CState.Idle || restart) {
this.pendingRestart = restart;
this.stop = false;
this.state = I2CState.Start;
this.onStart(restart);
return;
}
this.pendingRestart = false;
const cmd = this.txFIFO.pull();
const readMode = !!(cmd & CMD);
this.stop = !!(cmd & STOP);
if (readMode) {
this.onReadByte(!this.stop);
}
else {
this.onWriteByte(cmd & 0xff);
}
if (this.txFIFO.itemCount <= this.txThreshold) {
this.setInterrupts(R_TX_EMPTY);
}
}
pushRX(value) {
if (this.rxFIFO.full) {
this.setInterrupts(R_RX_OVER);
return;
}
this.rxFIFO.push(value);
if (this.rxFIFO.itemCount > this.rxThreshold) {
this.setInterrupts(R_RX_FULL);
}
}
completeStart() {
if (this.txFIFO.empty || this.state !== I2CState.Start || this.stop) {
this.onStop();
return;
}
const mode = this.txFIFO.peek() & CMD ? I2CMode.Read : I2CMode.Write;
this.state = I2CState.Connect;
this.setInterrupts(R_START_DET);
const addressMask = this.masterBits === 10 ? 0x3ff : 0xff;
this.onConnect(this.targetAddress & addressMask, mode);
}
completeConnect(ack, nackByte = 0) {
if (!ack || this.stop) {
if (!ack) {
if (!this.targetAddress) {
this.abort(ABRT_GCALL_NOACK);
}
else if (this.control & IC_10BITADDR_MASTER) {
this.abort(nackByte === 0 ? ABRT_10ADDR1_NOACK : ABRT_10ADDR2_NOACK);
}
else {
this.abort(ABRT_7B_ADDR_NOACK);
}
}
this.state = I2CState.Stop;
this.onStop();
return;
}
this.state = I2CState.Connected;
this.busy = false;
this.firstByte = true;
this.nextCommand();
}
completeWrite(ack) {
if (!ack || this.stop) {
if (!ack) {
this.abort(ABRT_TXDATA_NOACK);
}
this.state = I2CState.Stop;
this.onStop();
return;
}
this.busy = false;
this.nextCommand();
}
completeRead(value) {
this.pushRX(value | (this.firstByte ? FIRST_DATA_BYTE : 0));
if (this.stop) {
this.state = I2CState.Stop;
this.onStop();
return;
}
this.firstByte = false;
this.busy = false;
this.nextCommand();
}
completeStop() {
this.state = I2CState.Idle;
this.setInterrupts(R_STOP_DET);
this.busy = false;
this.pendingRestart = false;
if (this.enable & ABORT) {
this.enable &= ~ABORT;
}
else {
this.nextCommand();
}
}
arbitrationLost() {
this.state = I2CState.Idle;
this.busy = false;
this.abort(ARB_LOST);
}
readUint32(offset) {
switch (offset) {
case IC_CON:
return this.control;
case IC_TAR:
return this.targetAddress;
case IC_SAR:
return this.slaveAddress;
case IC_DATA_CMD:
if (this.rxFIFO.empty) {
this.setInterrupts(R_RX_UNDER);
return 0;
}
this.clearInterrupts(R_RX_FULL);
return this.rxFIFO.pull();
case IC_SS_SCL_HCNT:
return this.ssClockHighPeriod;
case IC_SS_SCL_LCNT:
return this.ssClockLowPeriod;
case IC_FS_SCL_HCNT:
return this.fsClockHighPeriod;
case IC_FS_SCL_LCNT:
return this.fsClockLowPeriod;
case IC_INTR_STAT:
return this.intStatus;
case IC_INTR_MASK:
return this.intEnable;
case IC_RAW_INTR_STAT:
return this.intRaw;
case IC_RX_TL:
return this.rxThreshold;
case IC_TX_TL:
return this.txThreshold;
case IC_CLR_INTR:
this.abortSource &= ABRT_SBYTE_NORSTRT; // Clear IC_TX_ABRT_SOURCE, expect for bit 9
return this.clearInterrupts(R_RX_UNDER |
R_RX_OVER |
R_TX_OVER |
R_RD_REQ |
R_TX_ABRT |
R_RX_DONE |
R_ACTIVITY |
R_STOP_DET |
R_START_DET |
R_GEN_CALL);
case IC_CLR_RX_UNDER:
return this.clearInterrupts(R_RX_UNDER);
case IC_CLR_RX_OVER:
return this.clearInterrupts(R_RX_OVER);
case IC_CLR_TX_OVER:
return this.clearInterrupts(R_TX_OVER);
case IC_CLR_RD_REQ:
return this.clearInterrupts(R_RD_REQ);
case IC_CLR_TX_ABRT:
this.abortSource &= ABRT_SBYTE_NORSTRT; // Clear IC_TX_ABRT_SOURCE, expect for bit 9
return this.clearInterrupts(R_TX_ABRT);
case IC_CLR_RX_DONE:
return this.clearInterrupts(R_RX_DONE);
case IC_CLR_ACTIVITY:
return this.clearInterrupts(R_ACTIVITY);
case IC_CLR_STOP_DET:
return this.clearInterrupts(R_STOP_DET);
case IC_CLR_START_DET:
return this.clearInterrupts(R_START_DET);
case IC_CLR_GEN_CALL:
return this.clearInterrupts(R_GEN_CALL);
case IC_ENABLE:
return this.enable;
case IC_STATUS:
return ((this.state !== I2CState.Idle ? MST_ACTIVITY | ACTIVITY : 0) |
(this.rxFIFO.full ? RFF : 0) |
(!this.rxFIFO.empty ? RFNE : 0) |
(this.txFIFO.empty ? TFE : 0) |
(!this.txFIFO.full ? TFNF : 0));
case IC_TXFLR:
return this.txFIFO.itemCount;
case IC_RXFLR:
return this.rxFIFO.itemCount;
case IC_SDA_HOLD:
return 0x01;
case IC_TX_ABRT_SOURCE: {
const value = this.abortSource;
this.abortSource &= ABRT_SBYTE_NORSTRT; // Clear IC_TX_ABRT_SOURCE, expect for bit 9
return value;
}
case IC_ENABLE_STATUS:
// I2C status - read only. bit 0 reflects IC_ENABLE, bit 1,2 relate to i2c slave mode.
return this.enable & 0x1;
case IC_FS_SPKLEN:
return this.spikelen & 0xff;
case IC_COMP_PARAM_1:
// From the datasheet:
// Note This register is not implemented and therefore reads as 0. If it was implemented it would be a constant read-only
// register that contains encoded information about the component's parameter settings.
return 0;
case IC_COMP_VERSION:
return 0x3230312a;
case IC_COMP_TYPE:
return 0x44570140;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
switch (offset) {
case IC_CON:
if (((value >> SPEED_SHIFT) & SPEED_MASK) === I2CSpeed.Invalid) {
value = (value & ~(SPEED_MASK << SPEED_SHIFT)) | (I2CSpeed.HighSpeedMode << SPEED_SHIFT);
}
this.control = value;
return;
case IC_TAR:
this.targetAddress = value & 0x3ff;
return;
case IC_SAR:
this.slaveAddress = value & 0x3ff;
return;
case IC_DATA_CMD:
if (this.txFIFO.full) {
this.setInterrupts(R_TX_OVER);
}
else {
this.txFIFO.push(value);
this.clearInterrupts(R_TX_EMPTY);
this.nextCommand();
}
return;
case IC_SS_SCL_HCNT:
this.ssClockHighPeriod = value & 0xffff;
return;
case IC_SS_SCL_LCNT:
this.ssClockLowPeriod = value & 0xffff;
return;
case IC_FS_SCL_HCNT:
this.fsClockHighPeriod = value & 0xffff;
return;
case IC_FS_SCL_LCNT:
this.fsClockLowPeriod = value & 0xffff;
return;
case IC_SDA_HOLD:
if (!(value & ENABLE)) {
if (value != 0x1) {
this.warn('Unimplemented write to IC_SDA_HOLD');
}
}
return;
case IC_RX_TL:
this.rxThreshold = value & 0xff;
if (this.rxThreshold > this.rxFIFO.size) {
this.rxThreshold = this.rxFIFO.size;
}
return;
case IC_TX_TL:
this.txThreshold = value & 0xff;
if (this.txThreshold > this.txFIFO.size) {
this.txThreshold = this.txFIFO.size;
}
return;
case IC_ENABLE:
// ABORT bit can only be set by software, not cleared.
value |= this.enable & ABORT;
if (value & ABORT) {
if (this.state === I2CState.Idle) {
value &= ~ABORT;
}
else {
this.abort(ABRT_USER_ABRT);
this.stop = true;
}
}
if (!(value & ENABLE)) {
this.txFIFO.reset();
this.rxFIFO.reset();
}
this.enable = value;
this.nextCommand(); // TX_CMD_BLOCK may have changed
return;
case IC_FS_SPKLEN:
if (!(value & ENABLE) && value > 0) {
this.spikelen = value;
}
return;
default:
super.writeUint32(offset, value);
}
}
}
+96
View File
@@ -0,0 +1,96 @@
import { BasePeripheral } from './peripheral.js';
const GPIO_CTRL_LAST = 0x0ec;
const INTR0 = 0xf0;
const PROC0_INTE0 = 0x100;
const PROC0_INTF0 = 0x110;
const PROC0_INTS0 = 0x120;
const PROC0_INTS3 = 0x12c;
export class RPIO extends BasePeripheral {
constructor(rp2040, name) {
super(rp2040, name);
}
getPinFromOffset(offset) {
const gpioIndex = offset >>> 3;
return {
gpio: this.rp2040.gpio[gpioIndex],
isCtrl: !!(offset & 0x4),
};
}
readUint32(offset) {
if (offset <= GPIO_CTRL_LAST) {
const { gpio, isCtrl } = this.getPinFromOffset(offset);
return isCtrl ? gpio.ctrl : gpio.status;
}
if (offset >= INTR0 && offset <= PROC0_INTS3) {
const startIndex = (offset & 0xf) * 2;
const register = offset & ~0xf;
const { gpio } = this.rp2040;
let result = 0;
for (let index = 7; index >= 0; index--) {
const pin = gpio[index + startIndex];
if (!pin) {
continue;
}
result <<= 4;
switch (register) {
case INTR0:
result |= pin.irqStatus;
break;
case PROC0_INTE0:
result |= pin.irqEnableMask;
break;
case PROC0_INTF0:
result |= pin.irqForceMask;
break;
case PROC0_INTS0:
result |= (pin.irqStatus & pin.irqEnableMask) | pin.irqForceMask;
break;
}
}
return result;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
if (offset <= GPIO_CTRL_LAST) {
const { gpio, isCtrl } = this.getPinFromOffset(offset);
if (isCtrl) {
gpio.ctrl = value;
gpio.checkForUpdates();
}
return;
}
if (offset >= INTR0 && offset <= PROC0_INTS3) {
const startIndex = (offset & 0xf) * 2;
const register = offset & ~0xf;
const { gpio } = this.rp2040;
for (let index = 0; index < 8; index++) {
const pin = gpio[index + startIndex];
if (!pin) {
continue;
}
const pinValue = (value >> (index * 4)) & 0xf;
const pinRawWriteValue = (this.rawWriteValue >> (index * 4)) & 0xf;
switch (register) {
case INTR0:
pin.updateIRQValue(pinRawWriteValue);
break;
case PROC0_INTE0:
if (pin.irqEnableMask !== pinValue) {
pin.irqEnableMask = pinValue;
this.rp2040.updateIOInterrupt();
}
break;
case PROC0_INTF0:
if (pin.irqForceMask !== pinValue) {
pin.irqForceMask = pinValue;
this.rp2040.updateIOInterrupt();
}
break;
}
}
return;
}
super.writeUint32(offset, value);
}
}
+54
View File
@@ -0,0 +1,54 @@
import { BasePeripheral } from './peripheral.js';
const VOLTAGE_SELECT = 0;
const GPIO_FIRST = 0x4;
const GPIO_LAST = 0x78;
const QSPI_FIRST = 0x4;
const QSPI_LAST = 0x18;
export class RPPADS extends BasePeripheral {
constructor(rp2040, name, bank) {
super(rp2040, name);
this.bank = bank;
this.voltageSelect = 0;
this.firstPadRegister = this.bank === 'qspi' ? QSPI_FIRST : GPIO_FIRST;
this.lastPadRegister = this.bank === 'qspi' ? QSPI_LAST : GPIO_LAST;
}
getPinFromOffset(offset) {
const gpioIndex = (offset - this.firstPadRegister) >>> 2;
if (this.bank === 'qspi') {
return this.rp2040.qspi[gpioIndex];
}
else {
return this.rp2040.gpio[gpioIndex];
}
}
readUint32(offset) {
if (offset >= this.firstPadRegister && offset <= this.lastPadRegister) {
const gpio = this.getPinFromOffset(offset);
return gpio.padValue;
}
switch (offset) {
case VOLTAGE_SELECT:
return this.voltageSelect;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
if (offset >= this.firstPadRegister && offset <= this.lastPadRegister) {
const gpio = this.getPinFromOffset(offset);
const oldInputEnable = gpio.inputEnable;
gpio.padValue = value;
gpio.checkForUpdates();
if (oldInputEnable !== gpio.inputEnable) {
gpio.refreshInput();
}
return;
}
switch (offset) {
case VOLTAGE_SELECT:
this.voltageSelect = value & 1;
break;
default:
super.writeUint32(offset, value);
}
}
}
+55
View File
@@ -0,0 +1,55 @@
const ATOMIC_NORMAL = 0;
const ATOMIC_XOR = 1;
const ATOMIC_SET = 2;
const ATOMIC_CLEAR = 3;
export function atomicUpdate(currentValue, atomicType, newValue) {
switch (atomicType) {
case ATOMIC_XOR:
return currentValue ^ newValue;
case ATOMIC_SET:
return currentValue | newValue;
case ATOMIC_CLEAR:
return currentValue & ~newValue;
default:
console.warn('Atomic update called with invalid writeType', atomicType);
return newValue;
}
}
export class BasePeripheral {
constructor(rp2040, name) {
this.rp2040 = rp2040;
this.name = name;
this.rawWriteValue = 0;
}
readUint32(offset) {
this.warn(`Unimplemented peripheral read from 0x${offset.toString(16)}`);
if (offset > 0x1000) {
this.warn('Unimplemented read from peripheral in the atomic operation region');
}
return 0xffffffff;
}
writeUint32(offset, value) {
this.warn(`Unimplemented peripheral write to 0x${offset.toString(16)}: 0x${value.toString(16)}`);
}
writeUint32Atomic(offset, value, atomicType) {
this.rawWriteValue = value;
const newValue = atomicType != ATOMIC_NORMAL
? atomicUpdate(this.readUint32(offset), atomicType, value)
: value;
this.writeUint32(offset, newValue);
}
debug(msg) {
this.rp2040.logger.debug(this.name, msg);
}
info(msg) {
this.rp2040.logger.info(this.name, msg);
}
warn(msg) {
this.rp2040.logger.warn(this.name, msg);
}
error(msg) {
this.rp2040.logger.error(this.name, msg);
}
}
export class UnimplementedPeripheral extends BasePeripheral {
}
File diff suppressed because it is too large Load Diff
+225
View File
@@ -0,0 +1,225 @@
import { MAX_HARDWARE_IRQ } from '../irq.js';
import { Timer32, Timer32PeriodicAlarm, TimerMode } from '../utils/timer32.js';
import { BasePeripheral } from './peripheral.js';
export const CPUID = 0xd00;
export const ICSR = 0xd04;
export const VTOR = 0xd08;
export const SHPR2 = 0xd1c;
export const SHPR3 = 0xd20;
const SYST_CSR = 0x010; // SysTick Control and Status Register
const SYST_RVR = 0x014; // SysTick Reload Value Register
const SYST_CVR = 0x018; // SysTick Current Value Register
const SYST_CALIB = 0x01c; // SysTick Calibration Value Register
const NVIC_ISER = 0x100; // Interrupt Set-Enable Register
const NVIC_ICER = 0x180; // Interrupt Clear-Enable Register
const NVIC_ISPR = 0x200; // Interrupt Set-Pending Register
const NVIC_ICPR = 0x280; // Interrupt Clear-Pending Register
// Interrupt priority registers:
const NVIC_IPR0 = 0x400;
const NVIC_IPR1 = 0x404;
const NVIC_IPR2 = 0x408;
const NVIC_IPR3 = 0x40c;
const NVIC_IPR4 = 0x410;
const NVIC_IPR5 = 0x414;
const NVIC_IPR6 = 0x418;
const NVIC_IPR7 = 0x41c;
/** ICSR Bits */
const NMIPENDSET = 1 << 31;
const PENDSVSET = 1 << 28;
const PENDSVCLR = 1 << 27;
const PENDSTSET = 1 << 26;
const PENDSTCLR = 1 << 25;
const ISRPREEMPT = 1 << 23;
const ISRPENDING = 1 << 22;
const VECTPENDING_MASK = 0x1ff;
const VECTPENDING_SHIFT = 12;
const VECTACTIVE_MASK = 0x1ff;
const VECTACTIVE_SHIFT = 0;
/** PPB stands for Private Periphral Bus.
* These are peripherals that are part of the ARM Cortex Core, and there's one copy for each processor core.
*
* Included peripheral: NVIC, SysTick timer
*/
export class RPPPB extends BasePeripheral {
constructor(rp2040, name) {
super(rp2040, name);
// Systick
this.systickCountFlag = false;
this.systickClkSource = false;
this.systickIntEnable = false;
this.systickReload = 0;
this.systickTimer = new Timer32(this.rp2040.clock, this.rp2040.clkSys);
this.systickAlarm = new Timer32PeriodicAlarm(this.systickTimer, () => {
this.systickCountFlag = true;
if (this.systickIntEnable) {
this.rp2040.core.pendingSystick = true;
this.rp2040.core.interruptsUpdated = true;
}
this.systickTimer.set(this.systickReload);
});
this.systickTimer.top = 0xffffff;
this.systickTimer.mode = TimerMode.Decrement;
this.systickAlarm.target = 0;
this.systickAlarm.enable = true;
this.reset();
}
reset() {
this.writeUint32(SYST_CSR, 0);
this.writeUint32(SYST_RVR, 0xffffff);
this.systickTimer.set(0xffffff);
}
readUint32(offset) {
const { rp2040 } = this;
const { core } = rp2040;
switch (offset) {
case CPUID:
return 0x410cc601; /* Verified against actual hardware */
case ICSR: {
const pendingInterrupts = core.pendingInterrupts || core.pendingPendSV || core.pendingSystick || core.pendingSVCall;
const vectPending = core.vectPending;
return ((core.pendingNMI ? NMIPENDSET : 0) |
(core.pendingPendSV ? PENDSVSET : 0) |
(core.pendingSystick ? PENDSTSET : 0) |
(pendingInterrupts ? ISRPENDING : 0) |
(vectPending << VECTPENDING_SHIFT) |
((core.IPSR & VECTACTIVE_MASK) << VECTACTIVE_SHIFT));
}
case VTOR:
return core.VTOR;
/* NVIC */
case NVIC_ISPR:
return core.pendingInterrupts >>> 0;
case NVIC_ICPR:
return core.pendingInterrupts >>> 0;
case NVIC_ISER:
return core.enabledInterrupts >>> 0;
case NVIC_ICER:
return core.enabledInterrupts >>> 0;
case NVIC_IPR0:
case NVIC_IPR1:
case NVIC_IPR2:
case NVIC_IPR3:
case NVIC_IPR4:
case NVIC_IPR5:
case NVIC_IPR6:
case NVIC_IPR7: {
const regIndex = (offset - NVIC_IPR0) >> 2;
let result = 0;
for (let byteIndex = 0; byteIndex < 4; byteIndex++) {
const interruptNumber = regIndex * 4 + byteIndex;
for (let priority = 0; priority < core.interruptPriorities.length; priority++) {
if (core.interruptPriorities[priority] & (1 << interruptNumber)) {
result |= priority << (8 * byteIndex + 6);
}
}
}
return result;
}
case SHPR2:
return core.SHPR2;
case SHPR3:
return core.SHPR3;
/* SysTick */
case SYST_CSR: {
const countFlagValue = this.systickCountFlag ? 1 << 16 : 0;
const clkSourceValue = this.systickClkSource ? 1 << 2 : 0;
const tickIntValue = this.systickIntEnable ? 1 << 1 : 0;
const enableFlagValue = this.systickTimer.enable ? 1 << 0 : 0;
this.systickCountFlag = false;
return countFlagValue | clkSourceValue | tickIntValue | enableFlagValue;
}
case SYST_CVR:
return this.systickTimer.counter;
case SYST_RVR:
return this.systickReload;
case SYST_CALIB:
return 0x0000270f;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
const { rp2040 } = this;
const { core } = rp2040;
const hardwareInterruptMask = (1 << MAX_HARDWARE_IRQ) - 1;
switch (offset) {
case ICSR:
if (value & NMIPENDSET) {
core.pendingNMI = true;
core.interruptsUpdated = true;
}
if (value & PENDSVSET) {
core.pendingPendSV = true;
core.interruptsUpdated = true;
}
if (value & PENDSVCLR) {
core.pendingPendSV = false;
}
if (value & PENDSTSET) {
core.pendingSystick = true;
core.interruptsUpdated = true;
}
if (value & PENDSTCLR) {
core.pendingSystick = false;
}
return;
case VTOR:
core.VTOR = value;
return;
/* NVIC */
case NVIC_ISPR:
core.pendingInterrupts |= value;
core.interruptsUpdated = true;
return;
case NVIC_ICPR:
core.pendingInterrupts &= ~value | hardwareInterruptMask;
return;
case NVIC_ISER:
core.enabledInterrupts |= value;
core.interruptsUpdated = true;
return;
case NVIC_ICER:
core.enabledInterrupts &= ~value;
return;
case NVIC_IPR0:
case NVIC_IPR1:
case NVIC_IPR2:
case NVIC_IPR3:
case NVIC_IPR4:
case NVIC_IPR5:
case NVIC_IPR6:
case NVIC_IPR7: {
const regIndex = (offset - NVIC_IPR0) >> 2;
for (let byteIndex = 0; byteIndex < 4; byteIndex++) {
const interruptNumber = regIndex * 4 + byteIndex;
const newPriority = (value >> (8 * byteIndex + 6)) & 0x3;
for (let priority = 0; priority < core.interruptPriorities.length; priority++) {
core.interruptPriorities[priority] &= ~(1 << interruptNumber);
}
core.interruptPriorities[newPriority] |= 1 << interruptNumber;
}
core.interruptsUpdated = true;
return;
}
case SHPR2:
core.SHPR2 = value;
return;
case SHPR3:
core.SHPR3 = value;
return;
// SysTick
case SYST_CSR:
this.systickClkSource = value & (1 << 2) ? true : false;
this.systickIntEnable = value & (1 << 1) ? true : false;
this.systickTimer.enable = value & (1 << 0) ? true : false;
return;
case SYST_CVR:
this.systickTimer.set(0);
return;
case SYST_RVR:
this.systickReload = value;
return;
default:
super.writeUint32(offset, value);
}
}
}
+44
View File
@@ -0,0 +1,44 @@
import { BasePeripheral } from './peripheral.js';
const FRCE_ON = 0x00;
const FRCE_OFF = 0x04;
const WDSEL = 0x08;
const DONE = 0x0c;
const PSM_BITS_MASK = 0x0001ffff;
export class RPPSM extends BasePeripheral {
constructor() {
super(...arguments);
this.frceOn = 0;
this.frceOff = 0;
this.wdsel = 0;
}
readUint32(offset) {
switch (offset) {
case FRCE_ON:
return this.frceOn;
case FRCE_OFF:
return this.frceOff;
case WDSEL:
return this.wdsel;
case DONE:
// Domains are ready unless forced off (FRCE_ON overrides FRCE_OFF)
return (PSM_BITS_MASK & ~this.frceOff) | (this.frceOn & this.frceOff);
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
switch (offset) {
case FRCE_ON:
this.frceOn = value & PSM_BITS_MASK;
break;
case FRCE_OFF:
this.frceOff = value & PSM_BITS_MASK;
break;
case WDSEL:
this.wdsel = value & PSM_BITS_MASK;
break;
default:
super.writeUint32(offset, value);
break;
}
}
}
+368
View File
@@ -0,0 +1,368 @@
import { IRQ } from '../irq.js';
import { Timer32, Timer32PeriodicAlarm, TimerMode } from '../utils/timer32.js';
import { DREQChannel } from './dma.js';
import { BasePeripheral } from './peripheral.js';
/** Control and status register */
const CHn_CSR = 0x00;
/**
* INT and FRAC form a fixed-point fractional number.
* Counting rate is system clock frequency divided by this number.
* Fractional division uses simple 1st-order sigma-delta.
*/
const CHn_DIV = 0x04;
/** Direct access to the PWM counter */
const CHn_CTR = 0x08;
/** Counter compare values */
const CHn_CC = 0x0c;
/** Counter wrap value */
const CHn_TOP = 0x10;
/**
* This register aliases the CSR_EN bits for all channels.
* Writing to this register allows multiple channels to be enabled
* or disabled simultaneously, so they can run in perfect sync.
* For each channel, there is only one physical EN register bit,
* which can be accessed through here or CHx_CSR.
*/
const EN = 0xa0;
/** Raw Interrupts */
const INTR = 0xa4;
/** Interrupt Enable */
const INTE = 0xa8;
/** Interrupt Force */
const INTF = 0xac;
/** Interrupt status after masking & forcing */
const INTS = 0xb0;
const INT_MASK = 0xff;
/* CHn_CSR bits */
const CSR_PH_ADV = 1 << 7;
const CSR_PH_RET = 1 << 6;
const CSR_DIVMODE_SHIFT = 4;
const CSR_DIVMODE_MASK = 0x3;
const CSR_B_INV = 1 << 3;
const CSR_A_INV = 1 << 2;
const CSR_PH_CORRECT = 1 << 1;
const CSR_EN = 1 << 0;
var PWMDivMode;
(function (PWMDivMode) {
PWMDivMode[PWMDivMode["FreeRunning"] = 0] = "FreeRunning";
PWMDivMode[PWMDivMode["BGated"] = 1] = "BGated";
PWMDivMode[PWMDivMode["BRisingEdge"] = 2] = "BRisingEdge";
PWMDivMode[PWMDivMode["BFallingEdge"] = 3] = "BFallingEdge";
})(PWMDivMode || (PWMDivMode = {}));
class PWMChannel {
constructor(pwm, clock, index) {
this.pwm = pwm;
this.clock = clock;
this.index = index;
this.timer = new Timer32(this.clock, this.pwm.clockFreq);
this.alarmA = new Timer32PeriodicAlarm(this.timer, () => {
this.setA(false);
});
this.alarmB = new Timer32PeriodicAlarm(this.timer, () => {
this.setB(false);
});
this.alarmBottom = new Timer32PeriodicAlarm(this.timer, () => this.wrap());
this.csr = 0;
this.div = 0;
this.cc = 0;
this.top = 0;
this.lastBValue = false;
this.countingUp = true;
this.ccUpdated = false;
this.topUpdated = false;
this.tickCounter = 0;
this.divMode = PWMDivMode.FreeRunning;
// GPIO pin indices: Table 525. Mapping of PWM channels to GPIO pins on RP2040
this.pinA1 = this.index * 2;
this.pinB1 = this.index * 2 + 1;
this.pinA2 = this.index < 7 ? 16 + this.index * 2 : -1;
this.pinB2 = this.index < 7 ? 16 + this.index * 2 + 1 : -1;
this.alarmA.enable = true;
this.alarmB.enable = true;
this.alarmBottom.enable = true;
}
readRegister(offset) {
switch (offset) {
case CHn_CSR:
return this.csr;
case CHn_DIV:
return this.div;
case CHn_CTR:
return this.timer.counter;
case CHn_CC:
return this.cc;
case CHn_TOP:
return this.top;
}
/* Shouldn't get here */
return 0;
}
writeRegister(offset, value) {
switch (offset) {
case CHn_CSR:
if (value & CSR_EN && !(this.csr & CSR_EN)) {
this.updateDoubleBuffered();
}
this.csr = value & ~(CSR_PH_ADV | CSR_PH_RET);
if (this.csr & CSR_PH_ADV) {
this.timer.advance(1);
}
if (this.csr & CSR_PH_RET) {
this.timer.advance(-1);
}
this.divMode = (this.csr >> CSR_DIVMODE_SHIFT) & CSR_DIVMODE_MASK;
this.setBDirection(this.divMode === PWMDivMode.FreeRunning);
this.updateEnable();
this.lastBValue = this.gpioBValue;
this.timer.mode = value & CSR_PH_CORRECT ? TimerMode.ZigZag : TimerMode.Increment;
break;
case CHn_DIV: {
this.div = value & 1048575;
const intValue = (value >> 4) & 0xff;
const fracValue = value & 0xf;
this.timer.prescaler = (intValue ? intValue : 256) + fracValue / 16;
break;
}
case CHn_CTR:
this.timer.set(value & 0xffff);
break;
case CHn_CC:
this.cc = value;
this.ccUpdated = true;
break;
case CHn_TOP:
this.top = value & 0xffff;
this.topUpdated = true;
break;
}
}
reset() {
this.writeRegister(CHn_CSR, 0);
this.writeRegister(CHn_DIV, 0x01 << 4);
this.writeRegister(CHn_CTR, 0);
this.writeRegister(CHn_CC, 0);
this.writeRegister(CHn_TOP, 0xffff);
this.countingUp = true;
this.timer.enable = false;
this.timer.reset();
}
updateDoubleBuffered() {
if (this.ccUpdated) {
this.alarmB.target = this.cc >>> 16;
this.alarmA.target = this.cc & 0xffff;
this.ccUpdated = false;
}
if (this.topUpdated) {
this.timer.top = this.top;
this.topUpdated = false;
}
}
wrap() {
this.pwm.channelInterrupt(this.index);
this.updateDoubleBuffered();
if (!(this.csr & CSR_PH_CORRECT)) {
this.setA(this.alarmA.target > 0);
this.setB(this.alarmB.target > 0);
}
}
setA(value) {
if (this.csr & CSR_A_INV) {
value = !value;
}
this.pwm.gpioSet(this.pinA1, value);
if (this.pinA2 >= 0) {
this.pwm.gpioSet(this.pinA2, value);
}
}
setB(value) {
if (this.csr & CSR_B_INV) {
value = !value;
}
this.pwm.gpioSet(this.pinB1, value);
if (this.pinB2 >= 0) {
this.pwm.gpioSet(this.pinB2, value);
}
}
get gpioBValue() {
return (this.pwm.gpioRead(this.pinB1) || (this.pinB2 > 0 ? this.pwm.gpioRead(this.pinB2) : false));
}
setBDirection(value) {
this.pwm.gpioSetDir(this.pinB1, value);
if (this.pinB2 >= 0) {
this.pwm.gpioSetDir(this.pinB2, value);
}
}
gpioBChanged() {
const value = this.gpioBValue;
if (value === this.lastBValue) {
return;
}
this.lastBValue = value;
switch (this.divMode) {
case PWMDivMode.BGated:
this.updateEnable();
break;
case PWMDivMode.BRisingEdge:
if (value) {
this.tickCounter++;
}
break;
case PWMDivMode.BFallingEdge:
if (!value) {
this.tickCounter++;
}
break;
}
if (this.tickCounter >= this.timer.prescaler) {
this.timer.advance(1);
this.tickCounter -= this.timer.prescaler;
}
}
updateEnable() {
const { csr, divMode } = this;
const enable = !!(csr & CSR_EN);
this.timer.enable =
enable &&
(divMode === PWMDivMode.FreeRunning || (divMode === PWMDivMode.BGated && this.gpioBValue));
}
set en(value) {
if (value && !(this.csr & CSR_EN)) {
this.updateDoubleBuffered();
}
if (value) {
this.csr |= CSR_EN;
}
else {
this.csr &= ~CSR_EN;
}
this.updateEnable();
}
}
export class RPPWM extends BasePeripheral {
constructor() {
super(...arguments);
this.channels = [
new PWMChannel(this, this.rp2040.clock, 0),
new PWMChannel(this, this.rp2040.clock, 1),
new PWMChannel(this, this.rp2040.clock, 2),
new PWMChannel(this, this.rp2040.clock, 3),
new PWMChannel(this, this.rp2040.clock, 4),
new PWMChannel(this, this.rp2040.clock, 5),
new PWMChannel(this, this.rp2040.clock, 6),
new PWMChannel(this, this.rp2040.clock, 7),
];
this.intRaw = 0;
this.intEnable = 0;
this.intForce = 0;
this.gpioValue = 0;
this.gpioDirection = 0;
}
get intStatus() {
return (this.intRaw & this.intEnable) | this.intForce;
}
readUint32(offset) {
if (offset < EN) {
const channel = Math.floor(offset / 0x14);
return this.channels[channel].readRegister(offset % 0x14);
}
switch (offset) {
case EN:
return ((this.channels[7].en << 7) |
(this.channels[6].en << 6) |
(this.channels[5].en << 5) |
(this.channels[4].en << 4) |
(this.channels[3].en << 3) |
(this.channels[2].en << 2) |
(this.channels[1].en << 1) |
(this.channels[0].en << 0));
case INTR:
return this.intRaw;
case INTE:
return this.intEnable;
case INTF:
return this.intForce;
case INTS:
return this.intStatus;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
if (offset < EN) {
const channel = Math.floor(offset / 0x14);
return this.channels[channel].writeRegister(offset % 0x14, value);
}
switch (offset) {
case EN:
this.channels[7].en = value & (1 << 7);
this.channels[6].en = value & (1 << 6);
this.channels[5].en = value & (1 << 5);
this.channels[4].en = value & (1 << 4);
this.channels[3].en = value & (1 << 3);
this.channels[2].en = value & (1 << 2);
this.channels[1].en = value & (1 << 1);
this.channels[0].en = value & (1 << 0);
break;
case INTR:
this.intRaw &= ~(value & INT_MASK);
this.checkInterrupts();
break;
case INTE:
this.intEnable = value & INT_MASK;
this.checkInterrupts();
break;
case INTF:
this.intForce = value & INT_MASK;
this.checkInterrupts();
break;
default:
super.writeUint32(offset, value);
}
}
get clockFreq() {
return this.rp2040.clkSys;
}
channelInterrupt(index) {
this.intRaw |= 1 << index;
this.checkInterrupts();
// We also set the DMA Request (DREQ) for the channel
this.rp2040.dma.setDREQ(DREQChannel.DREQ_PWM_WRAP0 + index);
}
checkInterrupts() {
this.rp2040.setInterrupt(IRQ.PWM_WRAP, !!this.intStatus);
}
gpioSet(index, value) {
const bit = 1 << index;
const newGpioValue = value ? this.gpioValue | bit : this.gpioValue & ~bit;
if (this.gpioValue != newGpioValue) {
this.gpioValue = newGpioValue;
this.rp2040.gpio[index].checkForUpdates();
}
}
gpioSetDir(index, output) {
const bit = 1 << index;
const newGpioDirection = output ? this.gpioDirection | bit : this.gpioDirection & ~bit;
if (this.gpioDirection != newGpioDirection) {
this.gpioDirection = newGpioDirection;
this.rp2040.gpio[index].checkForUpdates();
}
}
gpioRead(index) {
return this.rp2040.gpio[index].inputValue;
}
gpioOnInput(index) {
if (this.gpioDirection && 1 << index) {
return;
}
for (const channel of this.channels) {
if (channel.pinB1 === index || channel.pinB2 === index) {
channel.gpioBChanged();
}
}
}
reset() {
this.gpioDirection = 0xffffffff;
for (const channel of this.channels) {
channel.reset();
}
}
}
+36
View File
@@ -0,0 +1,36 @@
import { BasePeripheral } from './peripheral.js';
const RESET = 0x0; //Reset control.
const WDSEL = 0x4; //Watchdog select.
const RESET_DONE = 0x8; //Reset Done
export class RPReset extends BasePeripheral {
constructor() {
super(...arguments);
this.reset = 0;
this.wdsel = 0;
this.reset_done = 0x1ffffff;
}
readUint32(offset) {
switch (offset) {
case RESET:
return this.reset;
case WDSEL:
return this.wdsel;
case RESET_DONE:
return this.reset_done;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
switch (offset) {
case RESET:
this.reset = value & 0x1ffffff;
break;
case WDSEL:
this.wdsel = value & 0x1ffffff;
break;
default:
super.writeUint32(offset, value);
break;
}
}
}
+112
View File
@@ -0,0 +1,112 @@
import { BasePeripheral } from './peripheral.js';
const RTC_SETUP0 = 0x04;
const RTC_SETUP1 = 0x08;
const RTC_CTRL = 0x0c;
const IRQ_SETUP_0 = 0x10;
const RTC_RTC1 = 0x18;
const RTC_RTC0 = 0x1c;
const RTC_ENABLE_BITS = 0x01;
const RTC_ACTIVE_BITS = 0x2;
const RTC_LOAD_BITS = 0x10;
const SETUP_0_YEAR_SHIFT = 12;
const SETUP_0_YEAR_MASK = 0xfff;
const SETUP_0_MONTH_SHIFT = 8;
const SETUP_0_MONTH_MASK = 0xf;
const SETUP_0_DAY_SHIFT = 0;
const SETUP_0_DAY_MASK = 0x1f;
const SETUP_1_DOTW_SHIFT = 24;
const SETUP_1_DOTW_MASK = 0x7;
const SETUP_1_HOUR_SHIFT = 16;
const SETUP_1_HOUR_MASK = 0x1f;
const SETUP_1_MIN_SHIFT = 8;
const SETUP_1_MIN_MASK = 0x3f;
const SETUP_1_SEC_SHIFT = 0;
const SETUP_1_SEC_MASK = 0x3f;
const RTC_0_YEAR_SHIFT = 12;
const RTC_0_YEAR_MASK = 0xfff;
const RTC_0_MONTH_SHIFT = 8;
const RTC_0_MONTH_MASK = 0xf;
const RTC_0_DAY_SHIFT = 0;
const RTC_0_DAY_MASK = 0x1f;
const RTC_1_DOTW_SHIFT = 24;
const RTC_1_DOTW_MASK = 0x7;
const RTC_1_HOUR_SHIFT = 16;
const RTC_1_HOUR_MASK = 0x1f;
const RTC_1_MIN_SHIFT = 8;
const RTC_1_MIN_MASK = 0x3f;
const RTC_1_SEC_SHIFT = 0;
const RTC_1_SEC_MASK = 0x3f;
export class RP2040RTC extends BasePeripheral {
constructor() {
super(...arguments);
this.setup0 = 0;
this.setup1 = 0;
this.ctrl = 0;
this.baseline = new Date(2021, 0, 1);
this.baselineNanos = 0;
}
readUint32(offset) {
const date = new Date(this.baseline.getTime() + (this.rp2040.clock.nanos - this.baselineNanos) / 1000000);
switch (offset) {
case RTC_SETUP0:
return this.setup0;
case RTC_SETUP1:
return this.setup1;
case RTC_CTRL:
return this.ctrl;
case IRQ_SETUP_0:
return 0;
case RTC_RTC1:
return (((date.getFullYear() & RTC_0_YEAR_MASK) << RTC_0_YEAR_SHIFT) |
(((date.getMonth() + 1) & RTC_0_MONTH_MASK) << RTC_0_MONTH_SHIFT) |
((date.getDate() & RTC_0_DAY_MASK) << RTC_0_DAY_SHIFT));
case RTC_RTC0:
return (((date.getDay() & RTC_1_DOTW_MASK) << RTC_1_DOTW_SHIFT) |
((date.getHours() & RTC_1_HOUR_MASK) << RTC_1_HOUR_SHIFT) |
((date.getMinutes() & RTC_1_MIN_MASK) << RTC_1_MIN_SHIFT) |
((date.getSeconds() & RTC_1_SEC_MASK) << RTC_1_SEC_SHIFT));
default:
break;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
switch (offset) {
case RTC_SETUP0:
this.setup0 = value;
break;
case RTC_SETUP1:
this.setup1 = value;
break;
case RTC_CTRL:
// Though RTC_LOAD_BITS is type SC and should be cleared on next cycle, pico-sdk write
// RTC_LOAD_BITS & RTC_ENABLE_BITS seperatly.
// https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_rtc/rtc.c#L76-L80
if (value & RTC_LOAD_BITS) {
this.ctrl |= RTC_LOAD_BITS;
}
if (value & RTC_ENABLE_BITS) {
this.ctrl |= RTC_ENABLE_BITS;
this.ctrl |= RTC_ACTIVE_BITS;
if (this.ctrl & RTC_LOAD_BITS) {
const year = (this.setup0 >> SETUP_0_YEAR_SHIFT) & SETUP_0_YEAR_MASK;
const month = (this.setup0 >> SETUP_0_MONTH_SHIFT) & SETUP_0_MONTH_MASK;
const day = (this.setup0 >> SETUP_0_DAY_SHIFT) & SETUP_0_DAY_MASK;
const hour = (this.setup1 >> SETUP_1_HOUR_SHIFT) & SETUP_1_HOUR_MASK;
const min = (this.setup1 >> SETUP_1_MIN_SHIFT) & SETUP_1_MIN_MASK;
const sec = (this.setup1 >> SETUP_1_SEC_SHIFT) & SETUP_1_SEC_MASK;
this.baseline = new Date(year, month - 1, day, hour, min, sec);
this.baselineNanos = this.rp2040.clock.nanos;
this.ctrl &= ~RTC_LOAD_BITS;
}
}
else {
this.ctrl &= ~RTC_ENABLE_BITS;
this.ctrl &= ~RTC_ACTIVE_BITS;
}
break;
default:
super.writeUint32(offset, value);
}
}
}
+236
View File
@@ -0,0 +1,236 @@
import { FIFO } from '../utils/fifo.js';
import { BasePeripheral } from './peripheral.js';
const SSPCR0 = 0x000; // Control register 0, SSPCR0 on page 3-4
const SSPCR1 = 0x004; // Control register 1, SSPCR1 on page 3-5
const SSPDR = 0x008; // Data register, SSPDR on page 3-6
const SSPSR = 0x00c; // Status register, SSPSR on page 3-7
const SSPCPSR = 0x010; // Clock prescale register, SSPCPSR on page 3-8
const SSPIMSC = 0x014; // Interrupt mask set or clear register, SSPIMSC on page 3-9
const SSPRIS = 0x018; // Raw interrupt status register, SSPRIS on page 3-10
const SSPMIS = 0x01c; // Masked interrupt status register, SSPMIS on page 3-11
const SSPICR = 0x020; // Interrupt clear register, SSPICR on page 3-11
const SSPDMACR = 0x024; // DMA control register, SSPDMACR on page 3-12
const SSPPERIPHID0 = 0xfe0; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13
const SSPPERIPHID1 = 0xfe4; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13
const SSPPERIPHID2 = 0xfe8; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13
const SSPPERIPHID3 = 0xfec; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13
const SSPPCELLID0 = 0xff0; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16
const SSPPCELLID1 = 0xff4; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16
const SSPPCELLID2 = 0xff8; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16
const SSPPCELLID3 = 0xffc; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16
// SSPCR0 bits:
const SCR_MASK = 0xff;
const SCR_SHIFT = 8;
const SPH = 1 << 7;
const SPO = 1 << 6;
const FRF_MASK = 0x3;
const FRF_SHIFT = 4;
const DSS_MASK = 0xf;
const DSS_SHIFT = 0;
// SSPCR1 bits:
const SOD = 1 << 3;
const MS = 1 << 2;
const SSE = 1 << 1;
const LBM = 1 << 0;
// SSPSR bits:
const BSY = 1 << 4;
const RFF = 1 << 3;
const RNE = 1 << 2;
const TNF = 1 << 1;
const TFE = 1 << 0;
// SSPCPSR bits:
const CPSDVSR_MASK = 0xfe;
const CPSDVSR_SHIFT = 0;
// SSPDMACR bits:
const TXDMAE = 1 << 1;
const RXDMAE = 1 << 0;
// Interrupts:
const SSPTXINTR = 1 << 3;
const SSPRXINTR = 1 << 2;
const SSPRTINTR = 1 << 1;
const SSPRORINTR = 1 << 0;
export class RPSPI extends BasePeripheral {
get intStatus() {
return this.intRaw & this.intEnable;
}
get enabled() {
return !!(this.control1 & SSE);
}
/** Data size in bits: 4 to 16 bits */
get dataBits() {
return ((this.control0 >> DSS_SHIFT) & DSS_MASK) + 1;
}
get masterMode() {
return !(this.control0 & MS);
}
get spiMode() {
const cpol = this.control0 & SPO;
const cpha = this.control0 & SPH;
return cpol ? (cpha ? 2 : 3) : cpha ? 1 : 0;
}
get clockFrequency() {
if (!this.clockDivisor) {
return 0;
}
const scr = (this.control0 >> SCR_SHIFT) & SCR_MASK;
return this.rp2040.clkPeri / (this.clockDivisor * (1 + scr));
}
updateDMATx() {
if (this.txFIFO.full) {
this.rp2040.dma.clearDREQ(this.dreq.tx);
}
else {
this.rp2040.dma.setDREQ(this.dreq.tx);
}
}
updateDMARx() {
if (this.rxFIFO.empty) {
this.rp2040.dma.clearDREQ(this.dreq.rx);
}
else {
this.rp2040.dma.setDREQ(this.dreq.rx);
}
}
constructor(rp2040, name, irq, dreq) {
super(rp2040, name);
this.irq = irq;
this.dreq = dreq;
this.rxFIFO = new FIFO(8);
this.txFIFO = new FIFO(8);
// User provided callbacks
this.onTransmit = () => this.completeTransmit(0);
this.busy = false;
this.control0 = 0;
this.control1 = 0;
this.dmaControl = 0;
this.clockDivisor = 0;
this.intRaw = 0;
this.intEnable = 0;
this.updateDMATx();
this.updateDMARx();
}
doTX() {
if (!this.busy && !this.txFIFO.empty) {
const value = this.txFIFO.pull();
this.busy = true;
this.onTransmit(value);
this.fifosUpdated();
}
}
completeTransmit(rxValue) {
this.busy = false;
if (!this.rxFIFO.full) {
this.rxFIFO.push(rxValue);
}
else {
this.intRaw |= SSPRORINTR;
}
this.fifosUpdated();
this.doTX();
}
checkInterrupts() {
this.rp2040.setInterrupt(this.irq, !!this.intStatus);
}
fifosUpdated() {
const prevStatus = this.intStatus;
if (this.txFIFO.itemCount <= this.txFIFO.size / 2) {
this.intRaw |= SSPTXINTR;
}
else {
this.intRaw &= ~SSPTXINTR;
}
if (this.rxFIFO.itemCount >= this.rxFIFO.size / 2) {
this.intRaw |= SSPRXINTR;
}
else {
this.intRaw &= ~SSPRXINTR;
}
if (this.intStatus !== prevStatus) {
this.checkInterrupts();
}
this.updateDMATx();
this.updateDMARx();
}
readUint32(offset) {
switch (offset) {
case SSPCR0:
return this.control0;
case SSPCR1:
return this.control1;
case SSPDR:
if (!this.rxFIFO.empty) {
const value = this.rxFIFO.pull();
this.fifosUpdated();
return value;
}
return 0;
case SSPSR:
return ((this.busy || !this.txFIFO.empty ? BSY : 0) |
(this.rxFIFO.full ? RFF : 0) |
(!this.rxFIFO.empty ? RNE : 0) |
(!this.txFIFO.full ? TNF : 0) |
(this.txFIFO.empty ? TFE : 0));
case SSPCPSR:
return this.clockDivisor;
case SSPIMSC:
return this.intEnable;
case SSPRIS:
return this.intRaw;
case SSPMIS:
return this.intStatus;
case SSPDMACR:
return this.dmaControl;
case SSPPERIPHID0:
return 0x22;
case SSPPERIPHID1:
return 0x10;
case SSPPERIPHID2:
return 0x34;
case SSPPERIPHID3:
return 0x00;
case SSPPCELLID0:
return 0x0d;
case SSPPCELLID1:
return 0xf0;
case SSPPCELLID2:
return 0x05;
case SSPPCELLID3:
return 0xb1;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
switch (offset) {
case SSPCR0:
this.control0 = value;
return;
case SSPCR1:
this.control1 = value;
return;
case SSPDR:
if (!this.txFIFO.full) {
// decoded with respect to SSPCR0.DSS
this.txFIFO.push(value & ((1 << this.dataBits) - 1));
this.doTX();
this.fifosUpdated();
}
return;
case SSPCPSR:
this.clockDivisor = value & CPSDVSR_MASK;
return;
case SSPIMSC:
this.intEnable = value;
this.checkInterrupts();
return;
case SSPDMACR:
this.dmaControl = value;
return;
case SSPICR:
this.intRaw &= ~(value & (SSPRTINTR | SSPRORINTR));
this.checkInterrupts();
return;
default:
super.writeUint32(offset, value);
}
}
}
+119
View File
@@ -0,0 +1,119 @@
import { BasePeripheral } from './peripheral.js';
/* See RP2040 datasheet sect 4.10.13 */
const SSI_CTRLR0 = 0x00000000;
const SSI_CTRLR1 = 0x00000004;
const SSI_SSIENR = 0x00000008;
const SSI_MWCR = 0x0000000c;
const SSI_SER = 0x00000010;
const SSI_BAUDR = 0x00000014;
const SSI_TXFTLR = 0x00000018;
const SSI_RXFTLR = 0x0000001c;
const SSI_TXFLR = 0x00000020;
const SSI_RXFLR = 0x00000024;
const SSI_SR = 0x00000028;
const SSI_SR_TFNF_BITS = 0x00000002;
const SSI_SR_TFE_BITS = 0x00000004;
const SSI_SR_RFNE_BITS = 0x00000008;
const SSI_IMR = 0x0000002c;
const SSI_ISR = 0x00000030;
const SSI_RISR = 0x00000034;
const SSI_TXOICR = 0x00000038;
const SSI_RXOICR = 0x0000003c;
const SSI_RXUICR = 0x00000040;
const SSI_MSTICR = 0x00000044;
const SSI_ICR = 0x00000048;
const SSI_DMACR = 0x0000004c;
const SSI_DMATDLR = 0x00000050;
const SSI_DMARDLR = 0x00000054;
/** Identification register */
const SSI_IDR = 0x00000058;
const SSI_VERSION_ID = 0x0000005c;
const SSI_DR0 = 0x00000060;
const SSI_RX_SAMPLE_DLY = 0x000000f0;
const SSI_SPI_CTRL_R0 = 0x000000f4;
const SSI_TXD_DRIVE_EDGE = 0x000000f8;
const CMD_READ_STATUS = 0x05;
export class RPSSI extends BasePeripheral {
constructor() {
super(...arguments);
this.dr0 = 0;
this.txflr = 0;
this.rxflr = 0;
this.baudr = 0;
this.crtlr0 = 0;
this.crtlr1 = 0;
this.ssienr = 0;
this.spictlr0 = 0;
this.rxsampldly = 0;
this.txddriveedge = 0;
}
readUint32(offset) {
switch (offset) {
case SSI_TXFLR:
return this.txflr;
case SSI_RXFLR:
return this.rxflr;
case SSI_CTRLR0:
return this.crtlr0; /* & 0x017FFFFF = b23,b25..31 reserved */
case SSI_CTRLR1:
return this.crtlr1;
case SSI_SSIENR:
return this.ssienr;
case SSI_BAUDR:
return this.baudr;
case SSI_SR:
return SSI_SR_TFE_BITS | SSI_SR_RFNE_BITS | SSI_SR_TFNF_BITS;
case SSI_IDR:
return 0x51535049;
case SSI_VERSION_ID:
return 0x3430312a;
case SSI_RX_SAMPLE_DLY:
return this.rxsampldly;
case SSI_TXD_DRIVE_EDGE:
return this.txddriveedge;
case SSI_SPI_CTRL_R0:
return this.spictlr0; /* b6,7,10,19..23 reserved */
case SSI_DR0:
return this.dr0;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
switch (offset) {
case SSI_TXFLR:
this.txflr = value;
return;
case SSI_RXFLR:
this.rxflr = value;
return;
case SSI_CTRLR0:
this.crtlr0 = value; /* & 0x017FFFFF = b23,b25..31 reserved */
return;
case SSI_CTRLR1:
this.crtlr1 = value;
return;
case SSI_SSIENR:
this.ssienr = value;
return;
case SSI_BAUDR:
this.baudr = value;
return;
case SSI_RX_SAMPLE_DLY:
this.rxsampldly = value & 0xff;
return;
case SSI_TXD_DRIVE_EDGE:
this.txddriveedge = value & 0xff;
return;
case SSI_SPI_CTRL_R0:
this.spictlr0 = value;
return;
case SSI_DR0:
if (value === CMD_READ_STATUS) {
this.dr0 = 0; // tell stage2 that we completed a write
}
return;
default:
super.writeUint32(offset, value);
}
}
}
+22
View File
@@ -0,0 +1,22 @@
import { BasePeripheral } from './peripheral.js';
const PROC0_NMI_MASK = 0;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const PROC1_NMI_MASK = 4;
export class RP2040SysCfg extends BasePeripheral {
readUint32(offset) {
switch (offset) {
case PROC0_NMI_MASK:
return this.rp2040.core.interruptNMIMask;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
switch (offset) {
case PROC0_NMI_MASK:
this.rp2040.core.interruptNMIMask = value;
break;
default:
super.writeUint32(offset, value);
}
}
}
+18
View File
@@ -0,0 +1,18 @@
import { BasePeripheral } from './peripheral.js';
const CHIP_ID = 0;
const PLATFORM = 0x4;
const GITREF_RP2040 = 0x40;
export class RP2040SysInfo extends BasePeripheral {
readUint32(offset) {
// All the values here were verified against the silicon
switch (offset) {
case CHIP_ID:
return 0x10002927;
case PLATFORM:
return 0x00000002;
case GITREF_RP2040:
return 0xe0c912e8;
}
return super.readUint32(offset);
}
}
+13
View File
@@ -0,0 +1,13 @@
import { BasePeripheral } from './peripheral.js';
const PLATFORM = 0;
const ASIC = 1;
export class RPTBMAN extends BasePeripheral {
readUint32(offset) {
switch (offset) {
case PLATFORM:
return ASIC;
default:
return super.readUint32(offset);
}
}
}
+147
View File
@@ -0,0 +1,147 @@
import { IRQ } from '../irq.js';
import { BasePeripheral } from './peripheral.js';
const TIMEHR = 0x08;
const TIMELR = 0x0c;
const TIMERAWH = 0x24;
const TIMERAWL = 0x28;
const ALARM0 = 0x10;
const ALARM1 = 0x14;
const ALARM2 = 0x18;
const ALARM3 = 0x1c;
const ARMED = 0x20;
const PAUSE = 0x30;
const INTR = 0x34;
const INTE = 0x38;
const INTF = 0x3c;
const INTS = 0x40;
const ALARM_0 = 1 << 0;
const ALARM_1 = 1 << 1;
const ALARM_2 = 1 << 2;
const ALARM_3 = 1 << 3;
const timerInterrupts = [IRQ.TIMER_0, IRQ.TIMER_1, IRQ.TIMER_2, IRQ.TIMER_3];
class RPTimerAlarm {
constructor(bitValue, clockAlarm) {
this.bitValue = bitValue;
this.clockAlarm = clockAlarm;
this.armed = false;
this.targetMicros = 0;
}
}
export class RPTimer extends BasePeripheral {
constructor(rp2040, name) {
super(rp2040, name);
this.latchedTimeHigh = 0;
this.intRaw = 0;
this.intEnable = 0;
this.intForce = 0;
this.paused = false;
this.clock = rp2040.clock;
this.alarms = [
new RPTimerAlarm(ALARM_0, this.clock.createAlarm(() => this.fireAlarm(0))),
new RPTimerAlarm(ALARM_1, this.clock.createAlarm(() => this.fireAlarm(1))),
new RPTimerAlarm(ALARM_2, this.clock.createAlarm(() => this.fireAlarm(2))),
new RPTimerAlarm(ALARM_3, this.clock.createAlarm(() => this.fireAlarm(3))),
];
}
get intStatus() {
return (this.intRaw & this.intEnable) | this.intForce;
}
readUint32(offset) {
const time = this.clock.nanos / 1000;
switch (offset) {
case TIMEHR:
return this.latchedTimeHigh;
case TIMELR:
this.latchedTimeHigh = Math.floor(time / 2 ** 32);
return time >>> 0;
case TIMERAWH:
return Math.floor(time / 2 ** 32);
case TIMERAWL:
return time >>> 0;
case ALARM0:
return this.alarms[0].targetMicros;
case ALARM1:
return this.alarms[1].targetMicros;
case ALARM2:
return this.alarms[2].targetMicros;
case ALARM3:
return this.alarms[3].targetMicros;
case PAUSE:
return this.paused ? 1 : 0;
case INTR:
return this.intRaw;
case INTE:
return this.intEnable;
case INTF:
return this.intForce;
case INTS:
return this.intStatus;
case ARMED:
return ((this.alarms[0].armed ? this.alarms[0].bitValue : 0) |
(this.alarms[1].armed ? this.alarms[1].bitValue : 0) |
(this.alarms[2].armed ? this.alarms[2].bitValue : 0) |
(this.alarms[3].armed ? this.alarms[3].bitValue : 0));
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
switch (offset) {
case ALARM0:
case ALARM1:
case ALARM2:
case ALARM3: {
const alarmIndex = (offset - ALARM0) / 4;
const alarm = this.alarms[alarmIndex];
const deltaMicros = (value - this.clock.nanos / 1000) >>> 0;
alarm.armed = true;
alarm.targetMicros = value;
alarm.clockAlarm.schedule(deltaMicros * 1000);
break;
}
case ARMED:
for (const alarm of this.alarms) {
if (this.rawWriteValue & alarm.bitValue) {
this.disarmAlarm(alarm);
}
}
break;
case PAUSE:
this.paused = !!(value & 1);
if (this.paused) {
this.warn('Unimplemented Timer Pause');
}
// TODO actually pause the timer
break;
case INTR:
this.intRaw &= ~this.rawWriteValue;
this.checkInterrupts();
break;
case INTE:
this.intEnable = value & 0xf;
this.checkInterrupts();
break;
case INTF:
this.intForce = value & 0xf;
this.checkInterrupts();
break;
default:
super.writeUint32(offset, value);
}
}
fireAlarm(index) {
const alarm = this.alarms[index];
this.disarmAlarm(alarm);
this.intRaw |= alarm.bitValue;
this.checkInterrupts();
}
checkInterrupts() {
const { intStatus } = this;
for (let i = 0; i < this.alarms.length; i++) {
this.rp2040.setInterrupt(timerInterrupts[i], !!(intStatus & (1 << i)));
}
}
disarmAlarm(alarm) {
alarm.clockAlarm.cancel();
alarm.armed = false;
}
}
+180
View File
@@ -0,0 +1,180 @@
import { FIFO } from '../utils/fifo.js';
import { BasePeripheral } from './peripheral.js';
const UARTDR = 0x0;
const UARTFR = 0x18;
const UARTIBRD = 0x24;
const UARTFBRD = 0x28;
const UARTLCR_H = 0x2c;
const UARTCR = 0x30;
const UARTIMSC = 0x38;
const UARTIRIS = 0x3c;
const UARTIMIS = 0x40;
const UARTICR = 0x44;
const UARTPERIPHID0 = 0xfe0;
const UARTPERIPHID1 = 0xfe4;
const UARTPERIPHID2 = 0xfe8;
const UARTPERIPHID3 = 0xfec;
const UARTPCELLID0 = 0xff0;
const UARTPCELLID1 = 0xff4;
const UARTPCELLID2 = 0xff8;
const UARTPCELLID3 = 0xffc;
// UARTFR bits:
const TXFE = 1 << 7;
const RXFF = 1 << 6;
const RXFE = 1 << 4;
// UARTLCR_H bits:
const FEN = 1 << 4;
// UARTCR bits:
const RXE = 1 << 9;
const TXE = 1 << 8;
const UARTEN = 1 << 0;
// Interrupt bits
const UARTTXINTR = 1 << 5;
const UARTRXINTR = 1 << 4;
export class RPUART extends BasePeripheral {
constructor(rp2040, name, irq, dreq) {
super(rp2040, name);
this.irq = irq;
this.dreq = dreq;
this.ctrlRegister = RXE | TXE;
this.lineCtrlRegister = 0;
this.rxFIFO = new FIFO(32);
this.interruptMask = 0;
this.interruptStatus = 0;
this.intDivisor = 0;
this.fracDivisor = 0;
}
get enabled() {
return !!(this.ctrlRegister & UARTEN);
}
get txEnabled() {
return !!(this.ctrlRegister & TXE);
}
get rxEnabled() {
return !!(this.ctrlRegister & RXE);
}
get fifosEnabled() {
return !!(this.lineCtrlRegister & FEN);
}
/**
* Number of bits per UART character
*/
get wordLength() {
switch ((this.lineCtrlRegister >>> 5) & 0x3) {
case 0b00:
return 5;
case 0b01:
return 6;
case 0b10:
return 7;
case 0b11:
return 8;
}
}
get baudDivider() {
return this.intDivisor + this.fracDivisor / 64;
}
get baudRate() {
return Math.round(this.rp2040.clkPeri / (this.baudDivider * 16));
}
get flags() {
return (this.rxFIFO.full ? RXFF : 0) | (this.rxFIFO.empty ? RXFE : 0) | TXFE;
}
checkInterrupts() {
// TODO We should actually implement a proper FIFO for TX
this.interruptStatus |= UARTTXINTR;
this.rp2040.setInterrupt(this.irq, !!(this.interruptStatus & this.interruptMask));
}
feedByte(value) {
this.rxFIFO.push(value);
// TODO check if the FIFO has reached the threshold level
this.interruptStatus |= UARTRXINTR;
this.checkInterrupts();
}
readUint32(offset) {
switch (offset) {
case UARTDR: {
const value = this.rxFIFO.pull();
if (!this.rxFIFO.empty) {
this.interruptStatus |= UARTRXINTR;
}
else {
this.interruptStatus &= ~UARTRXINTR;
}
this.checkInterrupts();
return value;
}
case UARTFR:
return this.flags;
case UARTIBRD:
return this.intDivisor;
case UARTFBRD:
return this.fracDivisor;
case UARTLCR_H:
return this.lineCtrlRegister;
case UARTCR:
return this.ctrlRegister;
case UARTIMSC:
return this.interruptMask;
case UARTIRIS:
return this.interruptStatus;
case UARTIMIS:
return this.interruptStatus & this.interruptMask;
case UARTPERIPHID0:
return 0x11;
case UARTPERIPHID1:
return 0x10;
case UARTPERIPHID2:
return 0x34;
case UARTPERIPHID3:
return 0x00;
case UARTPCELLID0:
return 0x0d;
case UARTPCELLID1:
return 0xf0;
case UARTPCELLID2:
return 0x05;
case UARTPCELLID3:
return 0xb1;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
var _a, _b, _c;
switch (offset) {
case UARTDR:
(_a = this.onByte) === null || _a === void 0 ? void 0 : _a.call(this, value & 0xff);
break;
case UARTIBRD:
this.intDivisor = value & 0xffff;
(_b = this.onBaudRateChange) === null || _b === void 0 ? void 0 : _b.call(this, this.baudRate);
break;
case UARTFBRD:
this.fracDivisor = value & 0x3f;
(_c = this.onBaudRateChange) === null || _c === void 0 ? void 0 : _c.call(this, this.baudRate);
break;
case UARTLCR_H:
this.lineCtrlRegister = value;
break;
case UARTCR:
this.ctrlRegister = value;
if (this.enabled) {
this.rp2040.dma.setDREQ(this.dreq.tx);
}
else {
this.rp2040.dma.clearDREQ(this.dreq.tx);
}
break;
case UARTIMSC:
this.interruptMask = value & 0x7ff;
this.checkInterrupts();
break;
case UARTICR:
this.interruptStatus &= ~this.rawWriteValue;
this.checkInterrupts();
break;
default:
super.writeUint32(offset, value);
}
}
}
+847
View File
@@ -0,0 +1,847 @@
import { IRQ } from '../irq.js';
import { parseSetupPacket, } from '../usb/usb-device.js';
import { BasePeripheral } from './peripheral.js';
const ENDPOINT_COUNT = 16;
const USB_HOST_INTERRUPT_ENDPOINTS = 15;
// USB DPSRAM Registers - Device mode
const EP1_IN_CONTROL = 0x8;
const EP0_IN_BUFFER_CONTROL = 0x80;
const EP0_OUT_BUFFER_CONTROL = 0x84;
const EP15_OUT_BUFFER_CONTROL = 0xfc;
// USB DPRAM Registers - Host mode
// Host DPRAM layout (from pico-sdk hardware/structs/usb.h):
// 0x00-0x07: setup_packet (8 bytes)
// 0x08-0x7f: int_ep_ctrl[15] (15 × 8 bytes, ctrl + spare per entry)
// 0x80: epx_buf_ctrl (4 bytes)
// 0x84: _spare0 (4 bytes)
// 0x88-0xff: int_ep_buffer_ctrl[15] (15 × 8 bytes, ctrl + spare per entry)
// 0x100: epx_ctrl (4 bytes)
// 0x104-0x17f: _spare1 (124 bytes)
// 0x180+: epx_data buffer (up to end of DPRAM)
const HOST_SETUP_PACKET = 0x00;
const HOST_INT_EP_CTRL_BASE = 0x08; // int_ep_ctrl[0] at 0x08, stride 8
const HOST_INT_EP_BUF_CTRL_BASE = 0x88; // int_ep_buffer_ctrl[0] at 0x88, stride 8
const HOST_EPX_BUF_CTRL = 0x80;
const HOST_EPX_DATA = 0x180;
// Endpoint Control bits
const USB_CTRL_DOUBLE_BUF = 1 << 30;
const USB_CTRL_INTERRUPT_PER_TRANSFER = 1 << 29;
const EP_CTRL_ENABLE_BITS = 1 << 31;
const EP_CTRL_BUFFER_TYPE_LSB = 26;
const EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB = 16;
// Buffer Control bits
const USB_BUF_CTRL_AVAILABLE = 1 << 10;
const USB_BUF_CTRL_FULL = 1 << 15;
const USB_BUF_CTRL_LEN_MASK = 0x3ff;
const USB_BUF_CTRL_DATA1_PID = 1 << 13;
const USB_BUF_CTRL_LAST = 1 << 14;
// Buffer1
const USB_BUF1_SHIFT = 16;
const USB_BUF1_OFFSET = 64;
// USB Peripheral Registers
const ADDR_ENDP = 0x0;
const ADDR_ENDP1 = 0x04;
const ADDR_ENDP15 = 0x3c;
const MAIN_CTRL = 0x40;
const SOF_WR = 0x44;
const SOF_RD = 0x48;
const SIE_CTRL = 0x4c;
const SIE_STATUS = 0x50;
const INT_EP_CTRL = 0x54;
const BUFF_STATUS = 0x58;
const BUFF_CPU_SHOULD_HANDLE = 0x5c;
const EP_ABORT = 0x60;
const EP_ABORT_DONE = 0x64;
const EP_STALL_ARM = 0x68;
const NAK_POLL = 0x6c;
const EP_STATUS_STALL_NAK = 0x70;
const USB_MUXING = 0x74;
const USB_PWR = 0x78;
const USBPHY_DIRECT = 0x7c;
const USBPHY_DIRECT_OVERRIDE = 0x80;
const USBPHY_TRIM = 0x84;
const INTR = 0x8c;
const INTE = 0x90;
const INTF = 0x94;
const INTS = 0x98;
// MAIN_CTRL bits
const SIM_TIMING = 1 << 31;
const HOST_NDEVICE = 1 << 1;
const CONTROLLER_EN = 1 << 0;
// SIE_CTRL bits (host mode)
const SIE_CTRL_EP0_INT_STALL = 1 << 31;
const SIE_CTRL_EP0_DOUBLE_BUF = 1 << 30;
const SIE_CTRL_EP0_INT_1BUF = 1 << 29;
const SIE_CTRL_EP0_INT_2BUF = 1 << 28;
const SIE_CTRL_EP0_INT_NAK = 1 << 27;
const SIE_CTRL_DIRECT_EN = 1 << 26;
const SIE_CTRL_DIRECT_DP = 1 << 25;
const SIE_CTRL_DIRECT_DM = 1 << 24;
const SIE_CTRL_TRANSCEIVER_PD = 1 << 18;
const SIE_CTRL_RPU_OPT = 1 << 17;
const SIE_CTRL_PULLUP_EN = 1 << 16;
const SIE_CTRL_PULLDOWN_EN = 1 << 15;
const SIE_CTRL_RESET_BUS = 1 << 13;
const SIE_CTRL_RESUME = 1 << 12;
const SIE_CTRL_VBUS_EN = 1 << 11;
const SIE_CTRL_KEEP_ALIVE_EN = 1 << 10;
const SIE_CTRL_SOF_EN = 1 << 9;
const SIE_CTRL_SOF_SYNC = 1 << 8;
const SIE_CTRL_PREAMBLE_EN = 1 << 6;
const SIE_CTRL_STOP_TRANS = 1 << 4;
const SIE_CTRL_RECEIVE_DATA = 1 << 3;
const SIE_CTRL_SEND_DATA = 1 << 2;
const SIE_CTRL_SEND_SETUP = 1 << 1;
const SIE_CTRL_START_TRANS = 1 << 0;
// SIE_STATUS bits
const SIE_DATA_SEQ_ERROR = 1 << 31;
const SIE_ACK_REC = 1 << 30;
const SIE_STALL_REC = 1 << 29;
const SIE_NAK_REC = 1 << 28;
const SIE_RX_TIMEOUT = 1 << 27;
const SIE_RX_OVERFLOW = 1 << 26;
const SIE_BIT_STUFF_ERROR = 1 << 25;
const SIE_CRC_ERROR = 1 << 24;
const SIE_BUS_RESET = 1 << 19;
const SIE_TRANS_COMPLETE = 1 << 18;
const SIE_SETUP_REC = 1 << 17;
const SIE_CONNECTED = 1 << 16;
const SIE_RESUME = 1 << 11;
const SIE_VBUS_OVER_CURR = 1 << 10;
const SIE_SPEED = 1 << 9;
const SIE_SPEED_LS_VALUE = 1; // Low speed
const SIE_SPEED_FS_VALUE = 2; // Full speed
const SIE_SUSPENDED = 1 << 4;
const SIE_LINE_STATE_MASK = 0x3;
const SIE_LINE_STATE_SHIFT = 2;
const SIE_VBUS_DETECTED = 1 << 0;
// USB_MUXING bits
const SOFTCON = 1 << 3;
const TO_DIGITAL_PAD = 1 << 2;
const TO_EXTPHY = 1 << 1;
const TO_PHY = 1 << 0;
// USB_PWR bits
const PWR_VBUS_DETECT = 1 << 3;
const PWR_VBUS_DETECT_OVERRIDE_EN = 1 << 2;
const PWR_OVERCURR_DETECT = 1 << 1;
const PWR_OVERCURR_DETECT_EN = 1 << 0;
// INTR bits (directly from RP2040 datasheet)
const INTR_EP_STALL_NAK = 1 << 19;
const INTR_ABORT_DONE = 1 << 18;
const INTR_DEV_SOF = 1 << 17;
const INTR_SETUP_REQ = 1 << 16;
const INTR_DEV_RESUME_FROM_HOST = 1 << 15;
const INTR_DEV_SUSPEND = 1 << 14;
const INTR_DEV_CONN_DIS = 1 << 13;
const INTR_BUS_RESET = 1 << 12;
const INTR_VBUS_DETECT = 1 << 11;
const INTR_STALL = 1 << 10;
const INTR_ERROR_CRC = 1 << 9;
const INTR_ERROR_BIT_STUFF = 1 << 8;
const INTR_ERROR_RX_OVERFLOW = 1 << 7;
const INTR_ERROR_RX_TIMEOUT = 1 << 6;
const INTR_ERROR_DATA_SEQ = 1 << 5;
const INTR_BUFF_STATUS = 1 << 4;
const INTR_TRANS_COMPLETE = 1 << 3;
const INTR_HOST_SOF = 1 << 2;
const INTR_HOST_RESUME = 1 << 1;
const INTR_HOST_CONN_DIS = 1 << 0;
// SIE Line states
var SIELineState;
(function (SIELineState) {
SIELineState[SIELineState["SE0"] = 0] = "SE0";
SIELineState[SIELineState["J"] = 1] = "J";
SIELineState[SIELineState["K"] = 2] = "K";
SIELineState[SIELineState["SE1"] = 3] = "SE1";
})(SIELineState || (SIELineState = {}));
const SIE_WRITECLEAR_MASK = SIE_DATA_SEQ_ERROR |
SIE_ACK_REC |
SIE_STALL_REC |
SIE_NAK_REC |
SIE_RX_TIMEOUT |
SIE_RX_OVERFLOW |
SIE_BIT_STUFF_ERROR |
SIE_CONNECTED |
SIE_CRC_ERROR |
SIE_BUS_RESET |
SIE_TRANS_COMPLETE |
SIE_SETUP_REC |
SIE_RESUME;
class USBEndpointAlarm {
constructor(alarm) {
this.alarm = alarm;
this.buffers = [];
}
schedule(buffer, delayNanos) {
this.buffers.push(buffer);
this.alarm.schedule(delayNanos);
}
}
export class RPUSBController extends BasePeripheral {
get intStatus() {
return (this.intRaw & this.intEnable) | this.intForce;
}
constructor(rp2040, name) {
super(rp2040, name);
// Common registers
this.addrEndp = 0;
this.mainCtrl = 0;
this.intRaw = 0;
this.intEnable = 0;
this.intForce = 0;
this.sieStatus = 0;
this.buffStatus = 0;
// Host mode registers
this.sieCtrl = 0;
this.sofFrameNumber = 0;
this.devAddrCtrl = 0; // Device address and endpoint for non-interrupt transfers
this.intEpAddrCtrl = new Array(USB_HOST_INTERRUPT_ENDPOINTS).fill(0);
this.intEpCtrl = 0; // Interrupt endpoint control (enable bits)
this.usbPwr = 0;
this.nakPoll = 0;
this.epAbort = 0;
this.epAbortDone = 0;
this.epStallArm = 0;
this.epStatusStallNak = 0;
// Host mode state
this.hostMode = false;
this.sofEnabled = false;
this.connectedDevice = null;
this.pendingSetupResponse = null;
this.controlDataPid = 1; // DATA0/DATA1 toggle for control transfers
this.expectingStatusPhase = false; // True when expecting IN status for control OUT
this.readDelayMicroseconds = 10;
this.writeDelayMicroseconds = 10; // Determined empirically
this.hostTransactionDelayMicroseconds = 5; // Host transaction delay
const clock = rp2040.clock;
this.endpointReadAlarms = [];
this.endpointWriteAlarms = [];
for (let i = 0; i < ENDPOINT_COUNT; ++i) {
this.endpointReadAlarms.push(new USBEndpointAlarm(clock.createAlarm(() => {
const buffer = this.endpointReadAlarms[i].buffers.shift();
if (buffer) {
this.finishRead(i, buffer);
}
})));
this.endpointWriteAlarms.push(new USBEndpointAlarm(clock.createAlarm(() => {
var _a;
for (const buffer of this.endpointWriteAlarms[i].buffers) {
(_a = this.onEndpointWrite) === null || _a === void 0 ? void 0 : _a.call(this, i, buffer);
}
this.endpointWriteAlarms[i].buffers = [];
})));
}
this.resetAlarm = clock.createAlarm(() => {
this.sieStatus |= SIE_BUS_RESET;
this.sieStatusUpdated();
});
// Host mode alarms
this.sofAlarm = clock.createAlarm(() => {
this.generateSOF();
});
this.hostTransactionAlarm = clock.createAlarm(() => {
this.completeHostTransaction();
});
}
readUint32(offset) {
// Handle interrupt endpoint address registers (ADDR_ENDP1 through ADDR_ENDP15)
if (offset >= ADDR_ENDP1 && offset <= ADDR_ENDP15 && (offset & 0x3) === 0) {
const epIndex = (offset - ADDR_ENDP1) >> 2;
return this.intEpAddrCtrl[epIndex];
}
switch (offset) {
case ADDR_ENDP:
return this.hostMode ? this.devAddrCtrl : this.addrEndp & 0b1111000000001111111;
case MAIN_CTRL:
return this.mainCtrl;
case SOF_WR:
return 0; // Write-only
case SOF_RD:
return this.sofFrameNumber & 0x7ff;
case SIE_CTRL:
return this.sieCtrl;
case SIE_STATUS:
// In host mode, reading SIE_STATUS acknowledges the connection event
// Clear HOST_CONN_DIS interrupt once firmware reads the status
if (this.hostMode && this.intRaw & INTR_HOST_CONN_DIS) {
this.intRaw &= ~INTR_HOST_CONN_DIS;
this.checkInterrupts();
}
return this.sieStatus;
case INT_EP_CTRL:
return this.intEpCtrl;
case BUFF_STATUS:
return this.buffStatus;
case BUFF_CPU_SHOULD_HANDLE:
return 0;
case EP_ABORT:
return this.epAbort;
case EP_ABORT_DONE:
return this.epAbortDone;
case EP_STALL_ARM:
return this.epStallArm;
case NAK_POLL:
return this.nakPoll;
case EP_STATUS_STALL_NAK:
return this.epStatusStallNak;
case USB_PWR:
return this.usbPwr;
case INTR:
return this.intRaw;
case INTE:
return this.intEnable;
case INTF:
return this.intForce;
case INTS:
return this.intStatus;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
var _a, _b;
// Handle interrupt endpoint address registers (ADDR_ENDP1 through ADDR_ENDP15)
if (offset >= ADDR_ENDP1 && offset <= ADDR_ENDP15 && (offset & 0x3) === 0) {
const epIndex = (offset - ADDR_ENDP1) >> 2;
this.intEpAddrCtrl[epIndex] = value;
return;
}
switch (offset) {
case ADDR_ENDP:
if (this.hostMode) {
this.devAddrCtrl = value;
}
else {
this.addrEndp = value;
}
break;
case MAIN_CTRL:
this.mainCtrl = value & (SIM_TIMING | CONTROLLER_EN | HOST_NDEVICE);
this.hostMode = !!(value & HOST_NDEVICE);
if (value & CONTROLLER_EN) {
if (this.hostMode) {
this.debug('USB Host mode enabled');
// In host mode, check if a device is already connected
if (this.connectedDevice) {
this.onDeviceConnected();
}
}
else {
(_a = this.onUSBEnabled) === null || _a === void 0 ? void 0 : _a.call(this);
}
}
break;
case SOF_WR:
this.sofFrameNumber = value & 0x7ff;
break;
case SIE_CTRL:
this.handleSieCtrlWrite(value);
break;
case INT_EP_CTRL:
this.intEpCtrl = value;
break;
case BUFF_STATUS:
this.buffStatus &= ~this.rawWriteValue;
this.buffStatusUpdated();
break;
case EP_ABORT:
this.epAbort = value;
// Immediately mark as done
this.epAbortDone |= value;
break;
case EP_ABORT_DONE:
this.epAbortDone &= ~this.rawWriteValue;
break;
case EP_STALL_ARM:
this.epStallArm = value;
break;
case NAK_POLL:
this.nakPoll = value;
break;
case EP_STATUS_STALL_NAK:
this.epStatusStallNak &= ~this.rawWriteValue;
break;
case USB_MUXING:
// Workaround for busy wait in hw_enumeration_fix_force_ls_j() / hw_enumeration_fix_finish():
if (value & TO_DIGITAL_PAD && !(value & TO_PHY)) {
this.sieStatus |= SIE_CONNECTED;
}
break;
case USB_PWR:
this.usbPwr = value;
// VBUS detect override - set VBUS detected in SIE_STATUS
if (value & PWR_VBUS_DETECT_OVERRIDE_EN) {
if (value & PWR_VBUS_DETECT) {
this.sieStatus |= SIE_VBUS_DETECTED;
}
else {
this.sieStatus &= ~SIE_VBUS_DETECTED;
}
}
break;
case SIE_STATUS:
this.sieStatus &= ~(this.rawWriteValue & SIE_WRITECLEAR_MASK);
if (this.rawWriteValue & SIE_BUS_RESET) {
if (!this.hostMode) {
(_b = this.onResetReceived) === null || _b === void 0 ? void 0 : _b.call(this);
}
this.sieStatus &= ~(SIE_LINE_STATE_MASK << SIE_LINE_STATE_SHIFT);
this.sieStatus |= (SIELineState.J << SIE_LINE_STATE_SHIFT) | SIE_CONNECTED;
}
this.sieStatusUpdated();
break;
case INTE:
this.intEnable = value & 0xfffff;
this.checkInterrupts();
break;
case INTF:
this.intForce = value & 0xfffff;
this.checkInterrupts();
break;
default:
super.writeUint32(offset, value);
}
}
readEndpointControlReg(endpoint, out) {
const controlRegOffset = EP1_IN_CONTROL + 8 * (endpoint - 1) + (out ? 4 : 0);
return this.rp2040.usbDPRAMView.getUint32(controlRegOffset, true);
}
getEndpointBufferOffset(endpoint, out) {
if (endpoint === 0) {
return 0x100;
}
return this.readEndpointControlReg(endpoint, out) & 0xffc0;
}
DPRAMUpdated(offset, value) {
var _a, _b;
// Skip device-mode buffer control handling in host mode
if (this.hostMode) {
return;
}
if (value & USB_BUF_CTRL_AVAILABLE &&
offset >= EP0_IN_BUFFER_CONTROL &&
offset <= EP15_OUT_BUFFER_CONTROL) {
const endpoint = (offset - EP0_IN_BUFFER_CONTROL) >> 3;
const bufferOut = offset & 4 ? true : false;
let doubleBuffer = false;
let interrupt = true;
if (endpoint != 0) {
const control = this.readEndpointControlReg(endpoint, bufferOut);
doubleBuffer = !!(control & USB_CTRL_DOUBLE_BUF);
interrupt = !!(control & USB_CTRL_INTERRUPT_PER_TRANSFER);
}
if (doubleBuffer && (value >> USB_BUF1_SHIFT) & USB_BUF_CTRL_AVAILABLE) {
const bufferLength = (value >> USB_BUF1_SHIFT) & USB_BUF_CTRL_LEN_MASK;
const bufferOffset = this.getEndpointBufferOffset(endpoint, bufferOut) + USB_BUF1_OFFSET;
this.debug(`Start USB transfer, endPoint=${endpoint}, direction=${bufferOut ? 'out' : 'in'} buffer=${bufferOffset.toString(16)} length=${bufferLength}`);
value &= ~(USB_BUF_CTRL_AVAILABLE << USB_BUF1_SHIFT);
this.rp2040.usbDPRAMView.setUint32(offset, value, true);
if (bufferOut) {
(_a = this.onEndpointRead) === null || _a === void 0 ? void 0 : _a.call(this, endpoint, bufferLength);
}
else {
value &= ~(USB_BUF_CTRL_FULL << USB_BUF1_SHIFT);
this.rp2040.usbDPRAMView.setUint32(offset, value, true);
const buffer = this.rp2040.usbDPRAM.slice(bufferOffset, bufferOffset + bufferLength);
this.indicateBufferReady(endpoint, false);
this.endpointWriteAlarms[endpoint].schedule(buffer, this.writeDelayMicroseconds * 1000);
}
}
const bufferLength = value & USB_BUF_CTRL_LEN_MASK;
const bufferOffset = this.getEndpointBufferOffset(endpoint, bufferOut);
this.debug(`Start USB transfer, endPoint=${endpoint}, direction=${bufferOut ? 'out' : 'in'} buffer=${bufferOffset.toString(16)} length=${bufferLength}`);
value &= ~USB_BUF_CTRL_AVAILABLE;
this.rp2040.usbDPRAMView.setUint32(offset, value, true);
if (bufferOut) {
(_b = this.onEndpointRead) === null || _b === void 0 ? void 0 : _b.call(this, endpoint, bufferLength);
}
else {
value &= ~USB_BUF_CTRL_FULL;
this.rp2040.usbDPRAMView.setUint32(offset, value, true);
const buffer = this.rp2040.usbDPRAM.slice(bufferOffset, bufferOffset + bufferLength);
if (interrupt || !doubleBuffer) {
this.indicateBufferReady(endpoint, false);
}
this.endpointWriteAlarms[endpoint].schedule(buffer, this.writeDelayMicroseconds * 1000);
}
}
}
endpointReadDone(endpoint, buffer, delay = this.readDelayMicroseconds) {
this.endpointReadAlarms[endpoint].schedule(buffer, delay * 1000);
}
finishRead(endpoint, buffer) {
const bufferOffset = this.getEndpointBufferOffset(endpoint, true);
const bufControlReg = EP0_OUT_BUFFER_CONTROL + endpoint * 8;
let bufControl = this.rp2040.usbDPRAMView.getUint32(bufControlReg, true);
const requestedLength = bufControl & USB_BUF_CTRL_LEN_MASK;
const newLength = Math.min(buffer.length, requestedLength);
bufControl |= USB_BUF_CTRL_FULL;
bufControl = (bufControl & ~USB_BUF_CTRL_LEN_MASK) | (newLength & USB_BUF_CTRL_LEN_MASK);
this.rp2040.usbDPRAMView.setUint32(bufControlReg, bufControl, true);
this.rp2040.usbDPRAM.set(buffer.subarray(0, newLength), bufferOffset);
this.indicateBufferReady(endpoint, true);
}
checkInterrupts() {
const { intStatus } = this;
this.rp2040.setInterrupt(IRQ.USBCTRL, !!intStatus);
}
resetDevice() {
this.resetAlarm.schedule(10000000); // USB reset takes ~10ms
}
sendSetupPacket(setupPacket) {
this.rp2040.usbDPRAM.set(setupPacket);
this.sieStatus |= SIE_SETUP_REC;
this.sieStatusUpdated();
}
indicateBufferReady(endpoint, out) {
this.buffStatus |= 1 << (endpoint * 2 + (out ? 1 : 0));
this.buffStatusUpdated();
}
buffStatusUpdated() {
if (this.buffStatus) {
this.intRaw |= INTR_BUFF_STATUS;
}
else {
this.intRaw &= ~INTR_BUFF_STATUS;
}
this.checkInterrupts();
}
sieStatusUpdated() {
if (this.hostMode) {
// Host mode interrupt mapping
const intRegisterMap = [
[SIE_TRANS_COMPLETE, INTR_TRANS_COMPLETE],
[SIE_STALL_REC, INTR_STALL],
[SIE_CRC_ERROR, INTR_ERROR_CRC],
[SIE_BIT_STUFF_ERROR, INTR_ERROR_BIT_STUFF],
[SIE_RX_OVERFLOW, INTR_ERROR_RX_OVERFLOW],
[SIE_RX_TIMEOUT, INTR_ERROR_RX_TIMEOUT],
[SIE_DATA_SEQ_ERROR, INTR_ERROR_DATA_SEQ],
];
for (const [sieBit, intRawBit] of intRegisterMap) {
if (this.sieStatus & sieBit) {
this.intRaw |= intRawBit;
}
else {
this.intRaw &= ~intRawBit;
}
}
}
else {
// Device mode interrupt mapping
const intRegisterMap = [
[SIE_SETUP_REC, INTR_SETUP_REQ],
[SIE_RESUME, INTR_DEV_RESUME_FROM_HOST],
[SIE_SUSPENDED, INTR_DEV_SUSPEND],
[SIE_CONNECTED, INTR_DEV_CONN_DIS],
[SIE_BUS_RESET, INTR_BUS_RESET],
[SIE_VBUS_DETECTED, INTR_VBUS_DETECT],
[SIE_STALL_REC, INTR_STALL],
[SIE_CRC_ERROR, INTR_ERROR_CRC],
[SIE_BIT_STUFF_ERROR, INTR_ERROR_BIT_STUFF],
[SIE_RX_OVERFLOW, INTR_ERROR_RX_OVERFLOW],
[SIE_RX_TIMEOUT, INTR_ERROR_RX_TIMEOUT],
[SIE_DATA_SEQ_ERROR, INTR_ERROR_DATA_SEQ],
];
for (const [sieBit, intRawBit] of intRegisterMap) {
if (this.sieStatus & sieBit) {
this.intRaw |= intRawBit;
}
else {
this.intRaw &= ~intRawBit;
}
}
}
this.checkInterrupts();
}
// ============ Host Mode Methods ============
/**
* Connect a simulated USB device to the host controller.
*/
connectDevice(device) {
this.connectedDevice = device;
if (this.hostMode && this.mainCtrl & CONTROLLER_EN) {
this.onDeviceConnected();
}
}
/**
* Disconnect the simulated USB device from the host controller.
*/
disconnectDevice() {
if (this.connectedDevice && this.hostMode) {
this.connectedDevice = null;
// Clear speed bits to indicate disconnection
this.sieStatus &= ~(0x3 << 8); // Clear speed bits
this.intRaw |= INTR_HOST_CONN_DIS;
this.checkInterrupts();
}
this.connectedDevice = null;
}
onDeviceConnected() {
// Set full-speed device connected (value 2 in speed field, bits 9:8)
this.sieStatus &= ~(0x3 << 8);
this.sieStatus |= SIE_SPEED_FS_VALUE << 8;
this.intRaw |= INTR_HOST_CONN_DIS;
this.checkInterrupts();
this.debug('USB device connected (full-speed)');
}
handleSieCtrlWrite(value) {
this.sieCtrl = value;
// Handle SOF enable/disable
if (value & SIE_CTRL_SOF_EN && !this.sofEnabled) {
this.sofEnabled = true;
this.scheduleSofPacket();
this.debug('SOF generation enabled');
}
else if (!(value & SIE_CTRL_SOF_EN) && this.sofEnabled) {
this.sofEnabled = false;
this.debug('SOF generation disabled');
}
// Handle bus reset
if (value & SIE_CTRL_RESET_BUS) {
this.debug('USB bus reset initiated');
if (this.connectedDevice) {
this.connectedDevice.onReset();
}
this.controlDataPid = 1; // Reset data toggle
}
// Handle start transaction
if (value & SIE_CTRL_START_TRANS) {
this.startHostTransaction();
}
}
scheduleSofPacket() {
if (this.sofEnabled) {
// SOF every 1ms = 1,000,000 ns
this.sofAlarm.schedule(1000000);
}
}
generateSOF() {
this.sofFrameNumber = (this.sofFrameNumber + 1) & 0x7ff;
this.intRaw |= INTR_HOST_SOF;
this.checkInterrupts();
// Poll interrupt endpoints
this.pollInterruptEndpoints();
// Schedule next SOF
this.scheduleSofPacket();
}
pollInterruptEndpoints() {
var _a;
// Debug: log if any interrupt endpoints are enabled
if (this.intEpCtrl && this.sofFrameNumber % 100 === 0) {
this.debug(`INT_EP poll: intEpCtrl=0x${this.intEpCtrl.toString(16)} sofFrame=${this.sofFrameNumber}`);
}
// Check each enabled interrupt endpoint
for (let i = 0; i < USB_HOST_INTERRUPT_ENDPOINTS; i++) {
const epCtrlBit = 1 << (i + 1);
if (!(this.intEpCtrl & epCtrlBit))
continue;
const addrEndp = this.intEpAddrCtrl[i];
const devAddr = addrEndp & 0x7f;
const epNum = (addrEndp >> 16) & 0xf;
const isOut = !!(addrEndp & (1 << 25)); // INTEP_DIR bit
// Debug: log endpoint config
if (this.sofFrameNumber % 500 === 0) {
this.debug(`INT_EP[${i}]: addrEndp=0x${addrEndp.toString(16)} devAddr=${devAddr} epNum=${epNum} connectedAddr=${(_a = this.connectedDevice) === null || _a === void 0 ? void 0 : _a.address}`);
}
if (!this.connectedDevice || this.connectedDevice.address !== devAddr)
continue;
// Get the endpoint control register for interval checking
const epCtrlOffset = HOST_INT_EP_CTRL_BASE + i * 8;
const epCtrl = this.rp2040.usbDPRAMView.getUint32(epCtrlOffset, true);
const interval = ((epCtrl >> EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB) & 0x1ff) + 1;
// Check if it's time to poll (simplified: poll every SOF for now)
if (this.sofFrameNumber % interval !== 0)
continue;
// Get buffer control
const bufCtrlOffset = HOST_INT_EP_BUF_CTRL_BASE + i * 8;
const bufCtrl = this.rp2040.usbDPRAMView.getUint32(bufCtrlOffset, true);
// Debug: log buffer status
if (this.sofFrameNumber % 500 === 0) {
this.debug(`INT_EP[${i}]: bufCtrl=0x${bufCtrl.toString(16)} available=${!!(bufCtrl & USB_BUF_CTRL_AVAILABLE)}`);
}
// Only poll if buffer is available
if (!(bufCtrl & USB_BUF_CTRL_AVAILABLE))
continue;
// For IN endpoints, request data from device
if (!isOut) {
const epAddr = 0x80 | epNum; // IN endpoint
const result = this.connectedDevice.handleDataIn(epAddr);
if (result.status === 'ack' && result.data) {
// Write data to the interrupt endpoint buffer
const bufferOffset = epCtrl & 0xffc0;
this.rp2040.usbDPRAM.set(result.data, bufferOffset);
// Update buffer control
let newBufCtrl = bufCtrl & ~USB_BUF_CTRL_AVAILABLE;
newBufCtrl |= USB_BUF_CTRL_FULL;
newBufCtrl =
(newBufCtrl & ~USB_BUF_CTRL_LEN_MASK) | (result.data.length & USB_BUF_CTRL_LEN_MASK);
this.rp2040.usbDPRAMView.setUint32(bufCtrlOffset, newBufCtrl, true);
// Set buffer status for this interrupt endpoint
// Interrupt EPs use bits 2+ in buff_status (bit 0 is epx IN, bit 1 is epx OUT)
this.buffStatus |= 1 << ((i + 1) * 2);
this.buffStatusUpdated();
}
}
}
}
startHostTransaction() {
if (!this.hostMode)
return;
const devAddr = this.devAddrCtrl & 0x7f;
const epNum = (this.devAddrCtrl >> 16) & 0xf;
this.debug(`Host transaction: dev=${devAddr} ep=${epNum} sieCtrl=0x${this.sieCtrl.toString(16)}`);
if (!this.connectedDevice) {
// No device connected - timeout
this.sieStatus |= SIE_RX_TIMEOUT;
this.sieStatusUpdated();
return;
}
// Determine transaction type
if (this.sieCtrl & SIE_CTRL_SEND_SETUP) {
this.handleSetupTransaction(devAddr);
}
else if (this.sieCtrl & SIE_CTRL_RECEIVE_DATA) {
this.handleInTransaction(devAddr, epNum);
}
else if (this.sieCtrl & SIE_CTRL_SEND_DATA) {
this.handleOutTransaction(devAddr, epNum);
}
}
handleSetupTransaction(_devAddr) {
var _a, _b;
// Read setup packet from DPRAM
const setupPacket = this.rp2040.usbDPRAM.slice(HOST_SETUP_PACKET, HOST_SETUP_PACKET + 8);
const setup = parseSetupPacket(setupPacket);
this.debug(`SETUP: bmRequestType=0x${setup.bmRequestType.toString(16)} bRequest=${setup.bRequest} wValue=0x${setup.wValue.toString(16)} wIndex=${setup.wIndex} wLength=${setup.wLength}`);
// Forward to device
const result = this.connectedDevice.handleSetupPacket(setupPacket);
// Handle SET_ADDRESS specially
if (setup.bRequest === 5 /* StandardRequest.SetAddress */ && setup.type === 0) {
const newAddr = setup.wValue & 0x7f;
this.connectedDevice.address = newAddr;
(_b = (_a = this.connectedDevice).onAddressAssigned) === null || _b === void 0 ? void 0 : _b.call(_a, newAddr);
this.debug(`Device address set to ${newAddr}`);
}
// Store response data for subsequent IN transaction
if (setup.direction === 'in' && result.data) {
this.pendingSetupResponse = result.data;
this.expectingStatusPhase = false;
}
else {
this.pendingSetupResponse = null;
// Control OUT transfer - next IN will be status phase (zero-length ACK)
this.expectingStatusPhase = true;
}
// Reset data toggle for data phase
this.controlDataPid = 1;
// SETUP always gets ACK (or STALL if error, but we handle that later)
this.sieStatus |= SIE_ACK_REC;
// Schedule transaction completion
this.hostTransactionAlarm.schedule(this.hostTransactionDelayMicroseconds * 1000);
}
handleInTransaction(devAddr, epNum) {
const epAddr = 0x80 | epNum;
let result;
if (epNum === 0 && this.expectingStatusPhase) {
// Control OUT status phase - return zero-length ACK
result = { status: 'ack', data: new Uint8Array(0) };
this.expectingStatusPhase = false;
this.debug('Control OUT status phase (ZLP)');
}
else if (epNum === 0 && this.pendingSetupResponse) {
// Control IN - return pending setup response
result = { status: 'ack', data: this.pendingSetupResponse };
// Get requested length from buffer control
const bufCtrl = this.rp2040.usbDPRAMView.getUint32(HOST_EPX_BUF_CTRL, true);
const maxLen = bufCtrl & USB_BUF_CTRL_LEN_MASK;
// Trim data to requested length
if (result.data && result.data.length > maxLen) {
result.data = result.data.slice(0, maxLen);
}
// Clear pending response if all data sent
if (!result.data || result.data.length <= maxLen) {
this.pendingSetupResponse = null;
}
}
else {
result = this.connectedDevice.handleDataIn(epAddr);
}
if (result.status === 'ack' && result.data) {
// Write data to EPX data buffer
this.rp2040.usbDPRAM.set(result.data, HOST_EPX_DATA);
// Update buffer control with actual length and FULL flag
let bufCtrl = this.rp2040.usbDPRAMView.getUint32(HOST_EPX_BUF_CTRL, true);
bufCtrl &= ~USB_BUF_CTRL_LEN_MASK;
bufCtrl |= result.data.length & USB_BUF_CTRL_LEN_MASK;
bufCtrl |= USB_BUF_CTRL_FULL;
bufCtrl &= ~USB_BUF_CTRL_AVAILABLE;
// Set DATA1 PID for control transfers
if (this.controlDataPid) {
bufCtrl |= USB_BUF_CTRL_DATA1_PID;
}
else {
bufCtrl &= ~USB_BUF_CTRL_DATA1_PID;
}
this.controlDataPid ^= 1;
this.rp2040.usbDPRAMView.setUint32(HOST_EPX_BUF_CTRL, bufCtrl, true);
// Set buffer status
this.buffStatus |= 1; // Bit 0 for EPX
this.buffStatusUpdated();
this.sieStatus |= SIE_ACK_REC;
}
else if (result.status === 'nak') {
this.sieStatus |= SIE_NAK_REC;
}
else if (result.status === 'stall') {
this.sieStatus |= SIE_STALL_REC;
}
this.hostTransactionAlarm.schedule(this.hostTransactionDelayMicroseconds * 1000);
}
handleOutTransaction(devAddr, epNum) {
const epAddr = epNum; // OUT endpoint
// Read data from EPX data buffer
const bufCtrl = this.rp2040.usbDPRAMView.getUint32(HOST_EPX_BUF_CTRL, true);
const dataLen = bufCtrl & USB_BUF_CTRL_LEN_MASK;
const data = this.rp2040.usbDPRAM.slice(HOST_EPX_DATA, HOST_EPX_DATA + dataLen);
let result;
if (epNum === 0 && dataLen === 0) {
// Zero-length status phase for control transfer
result = { status: 'ack' };
}
else {
result = this.connectedDevice.handleDataOut(epAddr, data);
}
// Update buffer control
let newBufCtrl = bufCtrl;
newBufCtrl &= ~USB_BUF_CTRL_AVAILABLE;
// Toggle DATA PID
if (this.controlDataPid) {
newBufCtrl |= USB_BUF_CTRL_DATA1_PID;
}
else {
newBufCtrl &= ~USB_BUF_CTRL_DATA1_PID;
}
this.controlDataPid ^= 1;
this.rp2040.usbDPRAMView.setUint32(HOST_EPX_BUF_CTRL, newBufCtrl, true);
if (result.status === 'ack') {
this.sieStatus |= SIE_ACK_REC;
this.buffStatus |= 1; // Bit 0 for EPX
this.buffStatusUpdated();
}
else if (result.status === 'nak') {
this.sieStatus |= SIE_NAK_REC;
}
else if (result.status === 'stall') {
this.sieStatus |= SIE_STALL_REC;
}
this.hostTransactionAlarm.schedule(this.hostTransactionDelayMicroseconds * 1000);
}
completeHostTransaction() {
// Clear START_TRANS bit
this.sieCtrl &= ~SIE_CTRL_START_TRANS;
// Set transaction complete
this.sieStatus |= SIE_TRANS_COMPLETE;
this.sieStatusUpdated();
this.debug('Host transaction complete');
}
}
+126
View File
@@ -0,0 +1,126 @@
import { Timer32, Timer32PeriodicAlarm, TimerMode } from '../utils/timer32.js';
import { BasePeripheral } from './peripheral.js';
const CTRL = 0x00; // Control register
const LOAD = 0x04; // Load the watchdog timer.
const REASON = 0x08; // Logs the reason for the last reset.
const SCRATCH0 = 0x0c; // Scratch register
const SCRATCH1 = 0x10; // Scratch register
const SCRATCH2 = 0x14; // Scratch register
const SCRATCH3 = 0x18; // Scratch register
const SCRATCH4 = 0x1c; // Scratch register
const SCRATCH5 = 0x20; // Scratch register
const SCRATCH6 = 0x24; // Scratch register
const SCRATCH7 = 0x28; // Scratch register
const TICK = 0x2c; // Controls the tick generator
// CTRL bits:
const TRIGGER = 1 << 31;
const ENABLE = 1 << 30;
const PAUSE_DBG1 = 1 << 26;
const PAUSE_DBG0 = 1 << 25;
const PAUSE_JTAG = 1 << 24;
const TIME_MASK = 0xffffff;
const TIME_SHIFT = 0;
// LOAD bits
const LOAD_MASK = 0xffffff;
const LOAD_SHIFT = 0;
// REASON bits:
const FORCE = 1 << 1;
const TIMER = 1 << 0;
// TICK bits:
const COUNT_MASK = 0x1ff;
const COUNT_SHIFT = 11;
const RUNNING = 1 << 10;
const TICK_ENABLE = 1 << 9;
const CYCLES_MASK = 0x1ff;
const CYCLES_SHIFT = 0;
const TICK_FREQUENCY = 2000000; // Actually 1 MHz, but due to errata RP2040-E1, the timer is decremented twice per tick
export class RPWatchdog extends BasePeripheral {
// User provided
constructor(rp2040, name) {
super(rp2040, name);
this.scratchData = new Uint32Array(8);
this.enable = false;
this.tickEnable = true;
this.reason = 0;
this.pauseDbg0 = true;
this.pauseDbg1 = true;
this.pauseJtag = true;
/** Called when the watchdog triggers - override with your own soft reset implementation */
this.onWatchdogTrigger = () => {
this.rp2040.logger.warn(this.name, 'Watchdog triggered, but no reset handler provided');
};
this.timer = new Timer32(rp2040.clock, TICK_FREQUENCY);
this.timer.mode = TimerMode.Decrement;
this.timer.enable = false;
this.alarm = new Timer32PeriodicAlarm(this.timer, () => {
var _a;
this.reason = TIMER;
(_a = this.onWatchdogTrigger) === null || _a === void 0 ? void 0 : _a.call(this);
});
this.alarm.target = 0;
this.alarm.enable = false;
}
readUint32(offset) {
switch (offset) {
case CTRL:
return ((this.timer.enable ? ENABLE : 0) |
(this.pauseDbg0 ? PAUSE_DBG0 : 0) |
(this.pauseDbg1 ? PAUSE_DBG1 : 0) |
(this.pauseJtag ? PAUSE_JTAG : 0) |
((this.timer.counter & TIME_MASK) << TIME_SHIFT));
case REASON:
return this.reason;
case SCRATCH0:
case SCRATCH1:
case SCRATCH2:
case SCRATCH3:
case SCRATCH4:
case SCRATCH5:
case SCRATCH6:
case SCRATCH7:
return this.scratchData[(offset - SCRATCH0) >> 2];
case TICK:
// TODO COUNT bits
return this.tickEnable ? RUNNING | TICK_ENABLE : 0;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
var _a;
switch (offset) {
case CTRL:
if (value & TRIGGER) {
this.reason = FORCE;
(_a = this.onWatchdogTrigger) === null || _a === void 0 ? void 0 : _a.call(this);
}
this.enable = !!(value & ENABLE);
this.timer.enable = this.enable && this.tickEnable;
this.alarm.enable = this.enable && this.tickEnable;
this.pauseDbg0 = !!(value & PAUSE_DBG0);
this.pauseDbg1 = !!(value & PAUSE_DBG1);
this.pauseJtag = !!(value & PAUSE_JTAG);
break;
case LOAD:
this.timer.set((value >>> LOAD_SHIFT) & LOAD_MASK);
break;
case SCRATCH0:
case SCRATCH1:
case SCRATCH2:
case SCRATCH3:
case SCRATCH4:
case SCRATCH5:
case SCRATCH6:
case SCRATCH7:
this.scratchData[(offset - SCRATCH0) >> 2] = value;
break;
case TICK:
this.tickEnable = !!(value & TICK_ENABLE);
this.timer.enable = this.enable && this.tickEnable;
this.alarm.enable = this.enable && this.tickEnable;
// TODO - handle CYCLES (tick also affectes timer)
break;
default:
super.writeUint32(offset, value);
}
}
}
+119
View File
@@ -0,0 +1,119 @@
import { BasePeripheral } from './peripheral.js';
// XOSC register offsets
const XOSC_CTRL = 0x00;
const XOSC_STATUS = 0x04;
const XOSC_DORMANT = 0x08;
const XOSC_STARTUP = 0x0c;
const XOSC_COUNT = 0x1c;
// CTRL register bits
const CTRL_ENABLE_LSB = 12;
const CTRL_ENABLE_BITS = 0x00fff000;
const CTRL_FREQ_RANGE_BITS = 0x00000fff;
// CTRL ENABLE values
const CTRL_ENABLE_DISABLE = 0xd1e;
const CTRL_ENABLE_ENABLE = 0xfab;
// STATUS register bits
const STATUS_STABLE = 0x80000000; // bit 31
const STATUS_BADWRITE = 0x01000000; // bit 24
const STATUS_ENABLED = 0x00001000; // bit 12
const STATUS_FREQ_RANGE_BITS = 0x00000003;
// DORMANT register values
const DORMANT_VALUE = 0x636f6d61; // "coma" in ASCII
const WAKE_VALUE = 0x77616b65; // "wake" in ASCII
// STARTUP register bits
const STARTUP_X4 = 0x00100000; // bit 20
const STARTUP_DELAY_BITS = 0x00003fff;
export class RPXOSC extends BasePeripheral {
constructor() {
super(...arguments);
this.ctrl = 0;
this.status = 0;
this.dormant = 0;
this.startup = 0;
this.count = 0;
this.enabled = false;
this.stable = false;
this.isDormant = false;
}
readUint32(offset) {
switch (offset) {
case XOSC_CTRL:
return this.ctrl;
case XOSC_STATUS: {
let status = this.status;
if (this.stable) {
status |= STATUS_STABLE;
}
if (this.enabled) {
status |= STATUS_ENABLED;
}
return status;
}
case XOSC_DORMANT:
return this.dormant;
case XOSC_STARTUP:
return this.startup;
case XOSC_COUNT:
return this.count;
}
return super.readUint32(offset);
}
writeUint32(offset, value) {
switch (offset) {
case XOSC_CTRL: {
this.ctrl = value;
const enableValue = (value & CTRL_ENABLE_BITS) >>> CTRL_ENABLE_LSB;
const freqRange = value & CTRL_FREQ_RANGE_BITS;
void freqRange; // Currently unused, but could be logged or validated
if (enableValue === CTRL_ENABLE_ENABLE) {
if (!this.isDormant) {
this.enabled = true;
// For simplicity, become stable immediately
// In real hardware, this would take time based on STARTUP register
this.stable = true;
}
}
else if (enableValue === CTRL_ENABLE_DISABLE) {
this.enabled = false;
this.stable = false;
}
else if (enableValue !== 0) {
// Invalid write to ENABLE field
this.status |= STATUS_BADWRITE;
this.warn(`Invalid ENABLE value written: 0x${enableValue.toString(16)}`);
}
break;
}
case XOSC_STATUS:
// Clear BADWRITE bit if written as 1 (write-1-to-clear)
if (value & STATUS_BADWRITE) {
this.status &= ~STATUS_BADWRITE;
}
break;
case XOSC_DORMANT:
if (value === DORMANT_VALUE) {
this.isDormant = true;
this.stable = false;
}
else if (value === WAKE_VALUE) {
this.isDormant = false;
if (this.enabled) {
this.stable = true;
}
}
this.dormant = value;
break;
case XOSC_STARTUP:
this.startup = value & (STARTUP_X4 | STARTUP_DELAY_BITS);
break;
case XOSC_COUNT:
// Writing to COUNT starts the countdown
this.count = value & 0xff;
// For simplicity, we don't actually implement the countdown
// In real hardware, this would decrement at the XOSC frequency
break;
default:
super.writeUint32(offset, value);
}
}
}
+341
View File
@@ -0,0 +1,341 @@
import { SimulationClock } from './clock/simulation-clock.js';
import { CortexM0Core } from './cortex-m0-core.js';
import { GPIOPin } from './gpio-pin.js';
import { IRQ } from './irq.js';
import { RPADC } from './peripherals/adc.js';
import { RPBUSCTRL } from './peripherals/busctrl.js';
import { RPClocks } from './peripherals/clocks.js';
import { DREQChannel, RPDMA } from './peripherals/dma.js';
import { RPI2C } from './peripherals/i2c.js';
import { RPIO } from './peripherals/io.js';
import { RPPADS } from './peripherals/pads.js';
import { UnimplementedPeripheral } from './peripherals/peripheral.js';
import { RPPIO } from './peripherals/pio.js';
import { RPPPB } from './peripherals/ppb.js';
import { RPPSM } from './peripherals/psm.js';
import { RPPWM } from './peripherals/pwm.js';
import { RPReset } from './peripherals/reset.js';
import { RP2040RTC } from './peripherals/rtc.js';
import { RPSPI } from './peripherals/spi.js';
import { RPSSI } from './peripherals/ssi.js';
import { RP2040SysCfg } from './peripherals/syscfg.js';
import { RP2040SysInfo } from './peripherals/sysinfo.js';
import { RPTBMAN } from './peripherals/tbman.js';
import { RPTimer } from './peripherals/timer.js';
import { RPUART } from './peripherals/uart.js';
import { RPUSBController } from './peripherals/usb.js';
import { RPWatchdog } from './peripherals/watchdog.js';
import { RPXOSC } from './peripherals/xosc.js';
import { RPSIO } from './sio.js';
import { ConsoleLogger, LogLevel } from './utils/logging.js';
export const FLASH_START_ADDRESS = 0x10000000;
export const FLASH_END_ADDRESS = 0x14000000;
export const RAM_START_ADDRESS = 0x20000000;
export const APB_START_ADDRESS = 0x40000000;
export const DPRAM_START_ADDRESS = 0x50100000;
export const SIO_START_ADDRESS = 0xd0000000;
const LOG_NAME = 'RP2040';
const KB = 1024;
const MB = 1024 * KB;
const MHz = 1000000;
export class RP2040 {
constructor(clock = new SimulationClock()) {
this.clock = clock;
this.bootrom = new Uint32Array(4 * KB);
this.sram = new Uint8Array(264 * KB);
this.sramView = new DataView(this.sram.buffer);
this.flash = new Uint8Array(16 * MB);
this.flash16 = new Uint16Array(this.flash.buffer);
this.flashView = new DataView(this.flash.buffer);
this.usbDPRAM = new Uint8Array(4 * KB);
this.usbDPRAMView = new DataView(this.usbDPRAM.buffer);
this.core = new CortexM0Core(this);
/* Clocks */
this.clkSys = 125 * MHz;
this.clkPeri = 125 * MHz;
this.ppb = new RPPPB(this, 'PPB');
this.sio = new RPSIO(this);
this.uart = [
new RPUART(this, 'UART0', IRQ.UART0, {
rx: DREQChannel.DREQ_UART0_RX,
tx: DREQChannel.DREQ_UART0_TX,
}),
new RPUART(this, 'UART1', IRQ.UART1, {
rx: DREQChannel.DREQ_UART1_RX,
tx: DREQChannel.DREQ_UART1_TX,
}),
];
this.i2c = [new RPI2C(this, 'I2C0', IRQ.I2C0), new RPI2C(this, 'I2C1', IRQ.I2C1)];
this.pwm = new RPPWM(this, 'PWM_BASE');
this.adc = new RPADC(this, 'ADC');
this.gpio = [
new GPIOPin(this, 0),
new GPIOPin(this, 1),
new GPIOPin(this, 2),
new GPIOPin(this, 3),
new GPIOPin(this, 4),
new GPIOPin(this, 5),
new GPIOPin(this, 6),
new GPIOPin(this, 7),
new GPIOPin(this, 8),
new GPIOPin(this, 9),
new GPIOPin(this, 10),
new GPIOPin(this, 11),
new GPIOPin(this, 12),
new GPIOPin(this, 13),
new GPIOPin(this, 14),
new GPIOPin(this, 15),
new GPIOPin(this, 16),
new GPIOPin(this, 17),
new GPIOPin(this, 18),
new GPIOPin(this, 19),
new GPIOPin(this, 20),
new GPIOPin(this, 21),
new GPIOPin(this, 22),
new GPIOPin(this, 23),
new GPIOPin(this, 24),
new GPIOPin(this, 25),
new GPIOPin(this, 26),
new GPIOPin(this, 27),
new GPIOPin(this, 28),
new GPIOPin(this, 29),
];
this.qspi = [
new GPIOPin(this, 0, 'SCLK'),
new GPIOPin(this, 1, 'SS'),
new GPIOPin(this, 2, 'SD0'),
new GPIOPin(this, 3, 'SD1'),
new GPIOPin(this, 4, 'SD2'),
new GPIOPin(this, 5, 'SD3'),
];
this.dma = new RPDMA(this, 'DMA');
this.pio = [
new RPPIO(this, 'PIO0', IRQ.PIO0_IRQ0, 0),
new RPPIO(this, 'PIO1', IRQ.PIO1_IRQ0, 1),
];
this.usbCtrl = new RPUSBController(this, 'USB');
this.spi = [
new RPSPI(this, 'SPI0', IRQ.SPI0, {
rx: DREQChannel.DREQ_SPI0_RX,
tx: DREQChannel.DREQ_SPI0_TX,
}),
new RPSPI(this, 'SPI1', IRQ.SPI1, {
rx: DREQChannel.DREQ_SPI1_RX,
tx: DREQChannel.DREQ_SPI1_TX,
}),
];
this.logger = new ConsoleLogger(LogLevel.Debug, true);
this.peripherals = {
0x18000: new RPSSI(this, 'SSI'),
0x40000: new RP2040SysInfo(this, 'SYSINFO_BASE'),
0x40004: new RP2040SysCfg(this, 'SYSCFG'),
0x40008: new RPClocks(this, 'CLOCKS_BASE'),
0x4000c: new RPReset(this, 'RESETS_BASE'),
0x40010: new RPPSM(this, 'PSM_BASE'),
0x40014: new RPIO(this, 'IO_BANK0_BASE'),
0x40018: new UnimplementedPeripheral(this, 'IO_QSPI_BASE'),
0x4001c: new RPPADS(this, 'PADS_BANK0_BASE', 'bank0'),
0x40020: new RPPADS(this, 'PADS_QSPI_BASE', 'qspi'),
0x40024: new RPXOSC(this, 'XOSC_BASE'),
0x40028: new UnimplementedPeripheral(this, 'PLL_SYS_BASE'),
0x4002c: new UnimplementedPeripheral(this, 'PLL_USB_BASE'),
0x40030: new RPBUSCTRL(this, 'BUSCTRL_BASE'),
0x40034: this.uart[0],
0x40038: this.uart[1],
0x4003c: this.spi[0],
0x40040: this.spi[1],
0x40044: this.i2c[0],
0x40048: this.i2c[1],
0x4004c: this.adc,
0x40050: this.pwm,
0x40054: new RPTimer(this, 'TIMER_BASE'),
0x40058: new RPWatchdog(this, 'WATCHDOG_BASE'),
0x4005c: new RP2040RTC(this, 'RTC_BASE'),
0x40060: new UnimplementedPeripheral(this, 'ROSC_BASE'),
0x40064: new UnimplementedPeripheral(this, 'VREG_AND_CHIP_RESET_BASE'),
0x4006c: new RPTBMAN(this, 'TBMAN_BASE'),
0x50000: this.dma,
0x50110: this.usbCtrl,
0x50200: this.pio[0],
0x50300: this.pio[1],
};
// Debugging
// eslint-disable-next-line @typescript-eslint/no-unused-vars
this.onBreak = (code) => {
// TODO: raise HardFault exception
// console.error('Breakpoint!', code);
};
this.reset();
}
loadBootrom(bootromData) {
this.bootrom.set(bootromData);
this.reset();
}
reset() {
this.core.reset();
this.pwm.reset();
this.flash.fill(0xff);
}
readUint32(address) {
address = address >>> 0; // round to 32-bits, unsigned
if (address & 0x3) {
this.logger.error(LOG_NAME, `read from address ${address.toString(16)}, which is not 32 bit aligned`);
}
const { bootrom } = this;
if (address < bootrom.length * 4) {
return bootrom[address / 4];
}
else if (address >= FLASH_START_ADDRESS && address < FLASH_END_ADDRESS) {
// Flash is mirrored four times:
// - 0x10000000 XIP
// - 0x11000000 XIP_NOALLOC
// - 0x12000000 XIP_NOCACHE
// - 0x13000000 XIP_NOCACHE_NOALLOC
const offset = address & 16777215;
return this.flashView.getUint32(offset, true);
}
else if (address >= RAM_START_ADDRESS && address < RAM_START_ADDRESS + this.sram.length) {
return this.sramView.getUint32(address - RAM_START_ADDRESS, true);
}
else if (address >= DPRAM_START_ADDRESS &&
address < DPRAM_START_ADDRESS + this.usbDPRAM.length) {
return this.usbDPRAMView.getUint32(address - DPRAM_START_ADDRESS, true);
}
else if (address >>> 12 === 0xe000e) {
return this.ppb.readUint32(address & 0xfff);
}
else if (address >= SIO_START_ADDRESS && address < SIO_START_ADDRESS + 0x10000000) {
return this.sio.readUint32(address - SIO_START_ADDRESS);
}
const peripheral = this.findPeripheral(address);
if (peripheral) {
return peripheral.readUint32(address & 0x3fff);
}
this.logger.warn(LOG_NAME, `Read from invalid memory address: ${address.toString(16)}`);
return 0xffffffff;
}
findPeripheral(address) {
return this.peripherals[(address >>> 14) << 2];
}
/** We assume the address is 16-bit aligned */
readUint16(address) {
if (address >= FLASH_START_ADDRESS && address < FLASH_START_ADDRESS + this.flash.length) {
return this.flashView.getUint16(address - FLASH_START_ADDRESS, true);
}
else if (address >= RAM_START_ADDRESS && address < RAM_START_ADDRESS + this.sram.length) {
return this.sramView.getUint16(address - RAM_START_ADDRESS, true);
}
const value = this.readUint32(address & 0xfffffffc);
return address & 0x2 ? (value & 0xffff0000) >>> 16 : value & 0xffff;
}
readUint8(address) {
if (address >= FLASH_START_ADDRESS && address < FLASH_START_ADDRESS + this.flash.length) {
return this.flash[address - FLASH_START_ADDRESS];
}
else if (address >= RAM_START_ADDRESS && address < RAM_START_ADDRESS + this.sram.length) {
return this.sram[address - RAM_START_ADDRESS];
}
const value = this.readUint16(address & 0xfffffffe);
return (address & 0x1 ? (value & 0xff00) >>> 8 : value & 0xff) >>> 0;
}
writeUint32(address, value) {
address = address >>> 0;
const { bootrom } = this;
const peripheral = this.findPeripheral(address);
if (peripheral) {
const atomicType = (address & 0x3000) >> 12;
const offset = address & 0xfff;
peripheral.writeUint32Atomic(offset, value, atomicType);
}
else if (address < bootrom.length * 4) {
bootrom[address / 4] = value;
}
else if (address >= FLASH_START_ADDRESS &&
address < FLASH_START_ADDRESS + this.flash.length) {
this.flashView.setUint32(address - FLASH_START_ADDRESS, value, true);
}
else if (address >= RAM_START_ADDRESS && address < RAM_START_ADDRESS + this.sram.length) {
this.sramView.setUint32(address - RAM_START_ADDRESS, value, true);
}
else if (address >= DPRAM_START_ADDRESS &&
address < DPRAM_START_ADDRESS + this.usbDPRAM.length) {
const offset = address - DPRAM_START_ADDRESS;
this.usbDPRAMView.setUint32(offset, value, true);
this.usbCtrl.DPRAMUpdated(offset, value);
}
else if (address >= SIO_START_ADDRESS && address < SIO_START_ADDRESS + 0x10000000) {
this.sio.writeUint32(address - SIO_START_ADDRESS, value);
}
else if (address >>> 12 === 0xe000e) {
this.ppb.writeUint32(address & 0xfff, value);
}
else {
this.logger.warn(LOG_NAME, `Write to undefined address: ${address.toString(16)}`);
}
}
writeUint8(address, value) {
if (address >= RAM_START_ADDRESS && address < RAM_START_ADDRESS + this.sram.length) {
this.sram[address - RAM_START_ADDRESS] = value;
return;
}
const alignedAddress = (address & 0xfffffffc) >>> 0;
const offset = address & 0x3;
const peripheral = this.findPeripheral(address);
if (peripheral) {
const atomicType = (alignedAddress & 0x3000) >> 12;
const offset = alignedAddress & 0xfff;
peripheral.writeUint32Atomic(offset, (value & 0xff) | ((value & 0xff) << 8) | ((value & 0xff) << 16) | ((value & 0xff) << 24), atomicType);
return;
}
const originalValue = this.readUint32(alignedAddress);
const newValue = new Uint32Array([originalValue]);
new DataView(newValue.buffer).setUint8(offset, value);
this.writeUint32(alignedAddress, newValue[0]);
}
writeUint16(address, value) {
// we assume that addess is 16-bit aligned.
// Ideally we should generate a fault if not!
if (address >= RAM_START_ADDRESS && address < RAM_START_ADDRESS + this.sram.length) {
this.sramView.setUint16(address - RAM_START_ADDRESS, value, true);
return;
}
const alignedAddress = (address & 0xfffffffc) >>> 0;
const offset = address & 0x3;
const peripheral = this.findPeripheral(address);
if (peripheral) {
const atomicType = (alignedAddress & 0x3000) >> 12;
const offset = alignedAddress & 0xfff;
peripheral.writeUint32Atomic(offset, (value & 0xffff) | ((value & 0xffff) << 16), atomicType);
return;
}
const originalValue = this.readUint32(alignedAddress);
const newValue = new Uint32Array([originalValue]);
new DataView(newValue.buffer).setUint16(offset, value, true);
this.writeUint32(alignedAddress, newValue[0]);
}
get gpioValues() {
const { gpio } = this;
let result = 0;
for (let gpioIndex = 0; gpioIndex < gpio.length; gpioIndex++) {
if (gpio[gpioIndex].inputValue) {
result |= 1 << gpioIndex;
}
}
return result;
}
setInterrupt(irq, value) {
this.core.setInterrupt(irq, value);
}
updateIOInterrupt() {
let interruptValue = false;
for (const pin of this.gpio) {
if (pin.irqValue) {
interruptValue = true;
}
}
this.setInterrupt(IRQ.IO_BANK0, interruptValue);
}
step() {
this.core.executeInstruction();
}
}
+41
View File
@@ -0,0 +1,41 @@
import { SimulationClock } from './clock/simulation-clock.js';
import { RP2040 } from './rp2040.js';
export class Simulator {
constructor(clock = new SimulationClock()) {
this.clock = clock;
this.executeTimer = null;
this.stopped = true;
this.rp2040 = new RP2040(clock);
this.rp2040.onBreak = () => this.stop();
}
execute() {
const { rp2040, clock } = this;
this.executeTimer = null;
this.stopped = false;
const cycleNanos = 1e9 / 125000000; // 125 MHz
for (let i = 0; i < 1000000 && !this.stopped; i++) {
if (rp2040.core.waiting) {
const { nanosToNextAlarm } = clock;
clock.tick(nanosToNextAlarm);
i += nanosToNextAlarm / cycleNanos;
}
else {
const cycles = rp2040.core.executeInstruction();
clock.tick(cycles * cycleNanos);
}
}
if (!this.stopped) {
this.executeTimer = setTimeout(() => this.execute(), 0);
}
}
stop() {
this.stopped = true;
if (this.executeTimer != null) {
clearTimeout(this.executeTimer);
this.executeTimer = null;
}
}
get executing() {
return !this.stopped;
}
}
+421
View File
@@ -0,0 +1,421 @@
import { Interpolator } from './interpolator.js';
const CPUID = 0x000;
// GPIO
const GPIO_IN = 0x004; // Input value for GPIO pins
const GPIO_HI_IN = 0x008; // Input value for QSPI pins
const GPIO_OUT = 0x010; // GPIO output value
const GPIO_OUT_SET = 0x014; // GPIO output value set
const GPIO_OUT_CLR = 0x018; // GPIO output value clear
const GPIO_OUT_XOR = 0x01c; // GPIO output value XOR
const GPIO_OE = 0x020; // GPIO output enable
const GPIO_OE_SET = 0x024; // GPIO output enable set
const GPIO_OE_CLR = 0x028; // GPIO output enable clear
const GPIO_OE_XOR = 0x02c; // GPIO output enable XOR
const GPIO_HI_OUT = 0x030; // QSPI output value
const GPIO_HI_OUT_SET = 0x034; // QSPI output value set
const GPIO_HI_OUT_CLR = 0x038; // QSPI output value clear
const GPIO_HI_OUT_XOR = 0x03c; // QSPI output value XOR
const GPIO_HI_OE = 0x040; // QSPI output enable
const GPIO_HI_OE_SET = 0x044; // QSPI output enable set
const GPIO_HI_OE_CLR = 0x048; // QSPI output enable clear
const GPIO_HI_OE_XOR = 0x04c; // QSPI output enable XOR
const GPIO_MASK = 0x3fffffff;
//HARDWARE DIVIDER
const DIV_UDIVIDEND = 0x060; // Divider unsigned dividend
const DIV_UDIVISOR = 0x064; // Divider unsigned divisor
const DIV_SDIVIDEND = 0x068; // Divider signed dividend
const DIV_SDIVISOR = 0x06c; // Divider signed divisor
const DIV_QUOTIENT = 0x070; // Divider result quotient
const DIV_REMAINDER = 0x074; //Divider result remainder
const DIV_CSR = 0x078;
//INTERPOLATOR
const INTERP0_ACCUM0 = 0x080; // Read/write access to accumulator 0
const INTERP0_ACCUM1 = 0x084; // Read/write access to accumulator 1
const INTERP0_BASE0 = 0x088; // Read/write access to BASE0 register
const INTERP0_BASE1 = 0x08c; // Read/write access to BASE1 register
const INTERP0_BASE2 = 0x090; // Read/write access to BASE2 register
const INTERP0_POP_LANE0 = 0x094; // Read LANE0 result, and simultaneously write lane results to both accumulators (POP)
const INTERP0_POP_LANE1 = 0x098; // Read LANE1 result, and simultaneously write lane results to both accumulators (POP)
const INTERP0_POP_FULL = 0x09c; // Read FULL result, and simultaneously write lane results to both accumulators (POP)
const INTERP0_PEEK_LANE0 = 0x0a0; // Read LANE0 result, without altering any internal state (PEEK)
const INTERP0_PEEK_LANE1 = 0x0a4; // Read LANE1 result, without altering any internal state (PEEK)
const INTERP0_PEEK_FULL = 0x0a8; // Read FULL result, without altering any internal state (PEEK)
const INTERP0_CTRL_LANE0 = 0x0ac; // Control register for lane 0
const INTERP0_CTRL_LANE1 = 0x0b0; // Control register for lane 1
const INTERP0_ACCUM0_ADD = 0x0b4; // Values written here are atomically added to ACCUM0
const INTERP0_ACCUM1_ADD = 0x0b8; // Values written here are atomically added to ACCUM1
const INTERP0_BASE_1AND0 = 0x0bc; // On write, the lower 16 bits go to BASE0, upper bits to BASE1 simultaneously
const INTERP1_ACCUM0 = 0x0c0; // Read/write access to accumulator 0
const INTERP1_ACCUM1 = 0x0c4; // Read/write access to accumulator 1
const INTERP1_BASE0 = 0x0c8; // Read/write access to BASE0 register
const INTERP1_BASE1 = 0x0cc; // Read/write access to BASE1 register
const INTERP1_BASE2 = 0x0d0; // Read/write access to BASE2 register
const INTERP1_POP_LANE0 = 0x0d4; // Read LANE0 result, and simultaneously write lane results to both accumulators (POP)
const INTERP1_POP_LANE1 = 0x0d8; // Read LANE1 result, and simultaneously write lane results to both accumulators (POP)
const INTERP1_POP_FULL = 0x0dc; // Read FULL result, and simultaneously write lane results to both accumulators (POP)
const INTERP1_PEEK_LANE0 = 0x0e0; // Read LANE0 result, without altering any internal state (PEEK)
const INTERP1_PEEK_LANE1 = 0x0e4; // Read LANE1 result, without altering any internal state (PEEK)
const INTERP1_PEEK_FULL = 0x0e8; // Read FULL result, without altering any internal state (PEEK)
const INTERP1_CTRL_LANE0 = 0x0ec; // Control register for lane 0
const INTERP1_CTRL_LANE1 = 0x0f0; // Control register for lane 1
const INTERP1_ACCUM0_ADD = 0x0f4; // Values written here are atomically added to ACCUM0
const INTERP1_ACCUM1_ADD = 0x0f8; // Values written here are atomically added to ACCUM1
const INTERP1_BASE_1AND0 = 0x0fc; // On write, the lower 16 bits go to BASE0, upper bits to BASE1 simultaneously
//SPINLOCK
const SPINLOCK_ST = 0x5c;
const SPINLOCK0 = 0x100;
const SPINLOCK31 = 0x17c;
export class RPSIO {
constructor(rp2040) {
this.rp2040 = rp2040;
this.gpioValue = 0;
this.gpioOutputEnable = 0;
this.qspiGpioValue = 0;
this.qspiGpioOutputEnable = 0;
this.divDividend = 0;
this.divDivisor = 1;
this.divQuotient = 0;
this.divRemainder = 0;
this.divCSR = 0;
this.spinLock = 0;
this.interp0 = new Interpolator(0);
this.interp1 = new Interpolator(1);
}
updateHardwareDivider(signed) {
if (this.divDivisor == 0) {
this.divQuotient = this.divDividend > 0 ? -1 : 1;
this.divRemainder = this.divDividend;
}
else {
if (signed) {
this.divQuotient = (this.divDividend | 0) / (this.divDivisor | 0);
this.divRemainder = (this.divDividend | 0) % (this.divDivisor | 0);
}
else {
this.divQuotient = (this.divDividend >>> 0) / (this.divDivisor >>> 0);
this.divRemainder = (this.divDividend >>> 0) % (this.divDivisor >>> 0);
}
}
this.divCSR = 0b11;
this.rp2040.core.cycles += 8;
}
readUint32(offset) {
if (offset >= SPINLOCK0 && offset <= SPINLOCK31) {
const bitIndexMask = 1 << ((offset - SPINLOCK0) / 4);
if (this.spinLock & bitIndexMask) {
return 0;
}
else {
this.spinLock |= bitIndexMask;
return bitIndexMask;
}
}
switch (offset) {
case GPIO_IN:
return this.rp2040.gpioValues;
case GPIO_HI_IN: {
const { qspi } = this.rp2040;
let result = 0;
for (let qspiIndex = 0; qspiIndex < qspi.length; qspiIndex++) {
if (qspi[qspiIndex].inputValue) {
result |= 1 << qspiIndex;
}
}
return result;
}
case GPIO_OUT:
return this.gpioValue;
case GPIO_OE:
return this.gpioOutputEnable;
case GPIO_HI_OUT:
return this.qspiGpioValue;
case GPIO_HI_OE:
return this.qspiGpioOutputEnable;
case GPIO_OUT_SET:
case GPIO_OUT_CLR:
case GPIO_OUT_XOR:
case GPIO_OE_SET:
case GPIO_OE_CLR:
case GPIO_OE_XOR:
case GPIO_HI_OUT_SET:
case GPIO_HI_OUT_CLR:
case GPIO_HI_OUT_XOR:
case GPIO_HI_OE_SET:
case GPIO_HI_OE_CLR:
case GPIO_HI_OE_XOR:
return 0; // TODO verify with silicone
case CPUID:
// Returns the current CPU core id (always 0 for now)
return 0;
case SPINLOCK_ST:
return this.spinLock;
case DIV_UDIVIDEND:
return this.divDividend;
case DIV_SDIVIDEND:
return this.divDividend;
case DIV_UDIVISOR:
return this.divDivisor;
case DIV_SDIVISOR:
return this.divDivisor;
case DIV_QUOTIENT:
this.divCSR &= ~0b10;
return this.divQuotient;
case DIV_REMAINDER:
return this.divRemainder;
case DIV_CSR:
return this.divCSR;
case INTERP0_ACCUM0:
return this.interp0.accum0;
case INTERP0_ACCUM1:
return this.interp0.accum1;
case INTERP0_BASE0:
return this.interp0.base0;
case INTERP0_BASE1:
return this.interp0.base1;
case INTERP0_BASE2:
return this.interp0.base2;
case INTERP0_CTRL_LANE0:
return this.interp0.ctrl0;
case INTERP0_CTRL_LANE1:
return this.interp0.ctrl1;
case INTERP0_PEEK_LANE0:
return this.interp0.result0;
case INTERP0_PEEK_LANE1:
return this.interp0.result1;
case INTERP0_PEEK_FULL:
return this.interp0.result2;
case INTERP0_POP_LANE0: {
const value = this.interp0.result0;
this.interp0.writeback();
return value;
}
case INTERP0_POP_LANE1: {
const value = this.interp0.result1;
this.interp0.writeback();
return value;
}
case INTERP0_POP_FULL: {
const value = this.interp0.result2;
this.interp0.writeback();
return value;
}
case INTERP0_ACCUM0_ADD:
return this.interp0.smresult0;
case INTERP0_ACCUM1_ADD:
return this.interp0.smresult1;
case INTERP1_ACCUM0:
return this.interp1.accum0;
case INTERP1_ACCUM1:
return this.interp1.accum1;
case INTERP1_BASE0:
return this.interp1.base0;
case INTERP1_BASE1:
return this.interp1.base1;
case INTERP1_BASE2:
return this.interp1.base2;
case INTERP1_CTRL_LANE0:
return this.interp1.ctrl0;
case INTERP1_CTRL_LANE1:
return this.interp1.ctrl1;
case INTERP1_PEEK_LANE0:
return this.interp1.result0;
case INTERP1_PEEK_LANE1:
return this.interp1.result1;
case INTERP1_PEEK_FULL:
return this.interp1.result2;
case INTERP1_POP_LANE0: {
const value = this.interp1.result0;
this.interp1.writeback();
return value;
}
case INTERP1_POP_LANE1: {
const value = this.interp1.result1;
this.interp1.writeback();
return value;
}
case INTERP1_POP_FULL: {
const value = this.interp1.result2;
this.interp1.writeback();
return value;
}
case INTERP1_ACCUM0_ADD:
return this.interp1.smresult0;
case INTERP1_ACCUM1_ADD:
return this.interp1.smresult1;
}
console.warn(`Read from invalid SIO address: ${offset.toString(16)}`);
return 0xffffffff;
}
writeUint32(offset, value) {
if (offset >= SPINLOCK0 && offset <= SPINLOCK31) {
const bitIndexMask = ~(1 << ((offset - SPINLOCK0) / 4));
this.spinLock &= bitIndexMask;
return;
}
const prevGpioValue = this.gpioValue;
const prevGpioOutputEnable = this.gpioOutputEnable;
switch (offset) {
case GPIO_OUT:
this.gpioValue = value & GPIO_MASK;
break;
case GPIO_OUT_SET:
this.gpioValue |= value & GPIO_MASK;
break;
case GPIO_OUT_CLR:
this.gpioValue &= ~value;
break;
case GPIO_OUT_XOR:
this.gpioValue ^= value & GPIO_MASK;
break;
case GPIO_OE:
this.gpioOutputEnable = value & GPIO_MASK;
break;
case GPIO_OE_SET:
this.gpioOutputEnable |= value & GPIO_MASK;
break;
case GPIO_OE_CLR:
this.gpioOutputEnable &= ~value;
break;
case GPIO_OE_XOR:
this.gpioOutputEnable ^= value & GPIO_MASK;
break;
case GPIO_HI_OUT:
this.qspiGpioValue = value & GPIO_MASK;
break;
case GPIO_HI_OUT_SET:
this.qspiGpioValue |= value & GPIO_MASK;
break;
case GPIO_HI_OUT_CLR:
this.qspiGpioValue &= ~value;
break;
case GPIO_HI_OUT_XOR:
this.qspiGpioValue ^= value & GPIO_MASK;
break;
case GPIO_HI_OE:
this.qspiGpioOutputEnable = value & GPIO_MASK;
break;
case GPIO_HI_OE_SET:
this.qspiGpioOutputEnable |= value & GPIO_MASK;
break;
case GPIO_HI_OE_CLR:
this.qspiGpioOutputEnable &= ~value;
break;
case GPIO_HI_OE_XOR:
this.qspiGpioOutputEnable ^= value & GPIO_MASK;
break;
case DIV_UDIVIDEND:
this.divDividend = value;
this.updateHardwareDivider(false);
break;
case DIV_SDIVIDEND:
this.divDividend = value;
this.updateHardwareDivider(true);
break;
case DIV_UDIVISOR:
this.divDivisor = value;
this.updateHardwareDivider(false);
break;
case DIV_SDIVISOR:
this.divDivisor = value;
this.updateHardwareDivider(true);
break;
case DIV_QUOTIENT:
this.divQuotient = value;
this.divCSR = 0b11;
break;
case DIV_REMAINDER:
this.divRemainder = value;
this.divCSR = 0b11;
break;
case INTERP0_ACCUM0:
this.interp0.accum0 = value;
this.interp0.update();
break;
case INTERP0_ACCUM1:
this.interp0.accum1 = value;
this.interp0.update();
break;
case INTERP0_BASE0:
this.interp0.base0 = value;
this.interp0.update();
break;
case INTERP0_BASE1:
this.interp0.base1 = value;
this.interp0.update();
break;
case INTERP0_BASE2:
this.interp0.base2 = value;
this.interp0.update();
break;
case INTERP0_CTRL_LANE0:
this.interp0.ctrl0 = value;
this.interp0.update();
break;
case INTERP0_CTRL_LANE1:
this.interp0.ctrl1 = value;
this.interp0.update();
break;
case INTERP0_ACCUM0_ADD:
this.interp0.accum0 += value;
this.interp0.update();
break;
case INTERP0_ACCUM1_ADD:
this.interp0.accum1 += value;
this.interp0.update();
break;
case INTERP0_BASE_1AND0:
this.interp0.setBase01(value);
break;
case INTERP1_ACCUM0:
this.interp1.accum0 = value;
this.interp1.update();
break;
case INTERP1_ACCUM1:
this.interp1.accum1 = value;
this.interp1.update();
break;
case INTERP1_BASE0:
this.interp1.base0 = value;
this.interp1.update();
break;
case INTERP1_BASE1:
this.interp1.base1 = value;
this.interp1.update();
break;
case INTERP1_BASE2:
this.interp1.base2 = value;
this.interp1.update();
break;
case INTERP1_CTRL_LANE0:
this.interp1.ctrl0 = value;
this.interp1.update();
break;
case INTERP1_CTRL_LANE1:
this.interp1.ctrl1 = value;
this.interp1.update();
break;
case INTERP1_ACCUM0_ADD:
this.interp1.accum0 += value;
this.interp1.update();
break;
case INTERP1_ACCUM1_ADD:
this.interp1.accum1 += value;
this.interp1.update();
break;
case INTERP1_BASE_1AND0:
this.interp1.setBase01(value);
break;
default:
console.warn(`Write to invalid SIO address: ${offset.toString(16)}, value=${value.toString(16)}`);
}
const pinsToUpdate = (this.gpioValue ^ prevGpioValue) | (this.gpioOutputEnable ^ prevGpioOutputEnable);
if (pinsToUpdate) {
const { gpio } = this.rp2040;
for (let gpioIndex = 0; gpioIndex < gpio.length; gpioIndex++) {
if (pinsToUpdate & (1 << gpioIndex)) {
gpio[gpioIndex].checkForUpdates();
}
}
}
}
}
+121
View File
@@ -0,0 +1,121 @@
import { FIFO } from '../utils/fifo.js';
import { DataDirection, DescriptorType, SetupRecipient, SetupType } from './interfaces.js';
import { createSetupPacket, getDescriptorPacket, setDeviceAddressPacket, setDeviceConfigurationPacket, } from './setup.js';
// CDC stuff
const CDC_REQUEST_SET_CONTROL_LINE_STATE = 0x22;
const CDC_DTR = 1 << 0;
const CDC_RTS = 1 << 1;
const CDC_DATA_CLASS = 10;
const ENDPOINT_BULK = 2;
const TX_FIFO_SIZE = 512;
const ENDPOINT_ZERO = 0;
const CONFIGURATION_DESCRIPTOR_SIZE = 9;
export function extractEndpointNumbers(descriptors) {
let index = 0;
let foundInterface = false;
const result = {
in: -1,
out: -1,
};
while (index < descriptors.length) {
const len = descriptors[index];
if (len < 2 || descriptors.length < index + len) {
break;
}
const type = descriptors[index + 1];
if (type === DescriptorType.Interface && len === 9) {
const numEndpoints = descriptors[index + 4];
const interfaceClass = descriptors[index + 5];
foundInterface = numEndpoints === 2 && interfaceClass === CDC_DATA_CLASS;
}
if (foundInterface && type === DescriptorType.Endpoint && len === 7) {
const address = descriptors[index + 2];
const attributes = descriptors[index + 3];
if ((attributes & 0x3) === ENDPOINT_BULK) {
if (address & 0x80) {
result.in = address & 0xf;
}
else {
result.out = address & 0xf;
}
}
}
index += descriptors[index];
}
return result;
}
export class USBCDC {
constructor(usb) {
this.usb = usb;
this.txFIFO = new FIFO(TX_FIFO_SIZE);
this.initialized = false;
this.descriptorsSize = null;
this.descriptors = [];
this.outEndpoint = -1;
this.inEndpoint = -1;
this.usb.onUSBEnabled = () => {
this.usb.resetDevice();
};
this.usb.onResetReceived = () => {
this.usb.sendSetupPacket(setDeviceAddressPacket(1));
};
this.usb.onEndpointWrite = (endpoint, buffer) => {
var _a, _b;
if (endpoint === ENDPOINT_ZERO && buffer.length === 0) {
if (this.descriptorsSize == null) {
this.usb.sendSetupPacket(getDescriptorPacket(DescriptorType.Configration, CONFIGURATION_DESCRIPTOR_SIZE));
}
// Acknowledgement
else if (!this.initialized) {
this.cdcSetControlLineState();
(_a = this.onDeviceConnected) === null || _a === void 0 ? void 0 : _a.call(this);
}
}
if (endpoint === ENDPOINT_ZERO && buffer.length > 1) {
if (buffer.length === CONFIGURATION_DESCRIPTOR_SIZE &&
buffer[1] === DescriptorType.Configration &&
this.descriptorsSize == null) {
this.descriptorsSize = (buffer[3] << 8) | buffer[2];
this.usb.sendSetupPacket(getDescriptorPacket(DescriptorType.Configration, this.descriptorsSize));
}
else if (this.descriptorsSize != null && this.descriptors.length < this.descriptorsSize) {
this.descriptors.push(...buffer);
}
if (this.descriptorsSize === this.descriptors.length) {
const endpoints = extractEndpointNumbers(this.descriptors);
this.inEndpoint = endpoints.in;
this.outEndpoint = endpoints.out;
// Now configure the device
this.usb.sendSetupPacket(setDeviceConfigurationPacket(1));
}
}
if (endpoint === this.inEndpoint) {
(_b = this.onSerialData) === null || _b === void 0 ? void 0 : _b.call(this, buffer);
}
};
this.usb.onEndpointRead = (endpoint, size) => {
if (endpoint === this.outEndpoint) {
const buffer = new Uint8Array(Math.min(size, this.txFIFO.itemCount));
for (let i = 0; i < buffer.length; i++) {
buffer[i] = this.txFIFO.pull();
}
this.usb.endpointReadDone(this.outEndpoint, buffer);
}
};
}
cdcSetControlLineState(value = CDC_DTR | CDC_RTS, interfaceNumber = 0) {
this.usb.sendSetupPacket(createSetupPacket({
dataDirection: DataDirection.HostToDevice,
type: SetupType.Class,
recipient: SetupRecipient.Device,
bRequest: CDC_REQUEST_SET_CONTROL_LINE_STATE,
wValue: value,
wIndex: interfaceNumber,
wLength: 0,
}));
this.initialized = true;
}
sendSerialByte(data) {
this.txFIFO.push(data);
}
}
+43
View File
@@ -0,0 +1,43 @@
export var DataDirection;
(function (DataDirection) {
DataDirection[DataDirection["HostToDevice"] = 0] = "HostToDevice";
DataDirection[DataDirection["DeviceToHost"] = 1] = "DeviceToHost";
})(DataDirection || (DataDirection = {}));
export var SetupType;
(function (SetupType) {
SetupType[SetupType["Standard"] = 0] = "Standard";
SetupType[SetupType["Class"] = 1] = "Class";
SetupType[SetupType["Vendor"] = 2] = "Vendor";
SetupType[SetupType["Reserved"] = 3] = "Reserved";
})(SetupType || (SetupType = {}));
export var SetupRecipient;
(function (SetupRecipient) {
SetupRecipient[SetupRecipient["Device"] = 0] = "Device";
SetupRecipient[SetupRecipient["Interface"] = 1] = "Interface";
SetupRecipient[SetupRecipient["Endpoint"] = 2] = "Endpoint";
SetupRecipient[SetupRecipient["Other"] = 3] = "Other";
})(SetupRecipient || (SetupRecipient = {}));
export var SetupRequest;
(function (SetupRequest) {
SetupRequest[SetupRequest["GetStatus"] = 0] = "GetStatus";
SetupRequest[SetupRequest["ClearFeature"] = 1] = "ClearFeature";
SetupRequest[SetupRequest["Reserved1"] = 2] = "Reserved1";
SetupRequest[SetupRequest["SetFeature"] = 3] = "SetFeature";
SetupRequest[SetupRequest["Reserved2"] = 4] = "Reserved2";
SetupRequest[SetupRequest["SetAddress"] = 5] = "SetAddress";
SetupRequest[SetupRequest["GetDescriptor"] = 6] = "GetDescriptor";
SetupRequest[SetupRequest["SetDescriptor"] = 7] = "SetDescriptor";
SetupRequest[SetupRequest["GetConfiguration"] = 8] = "GetConfiguration";
SetupRequest[SetupRequest["SetDeviceConfiguration"] = 9] = "SetDeviceConfiguration";
SetupRequest[SetupRequest["GetInterface"] = 10] = "GetInterface";
SetupRequest[SetupRequest["SetInterface"] = 11] = "SetInterface";
SetupRequest[SetupRequest["SynchFrame"] = 12] = "SynchFrame";
})(SetupRequest || (SetupRequest = {}));
export var DescriptorType;
(function (DescriptorType) {
DescriptorType[DescriptorType["Device"] = 1] = "Device";
DescriptorType[DescriptorType["Configration"] = 2] = "Configration";
DescriptorType[DescriptorType["String"] = 3] = "String";
DescriptorType[DescriptorType["Interface"] = 4] = "Interface";
DescriptorType[DescriptorType["Endpoint"] = 5] = "Endpoint";
})(DescriptorType || (DescriptorType = {}));
+46
View File
@@ -0,0 +1,46 @@
import { DataDirection, SetupRecipient, SetupRequest, SetupType, } from './interfaces.js';
export function createSetupPacket(params) {
const setupPacket = new Uint8Array(8);
setupPacket[0] = (params.dataDirection << 7) | (params.type << 5) | params.recipient;
setupPacket[1] = params.bRequest;
setupPacket[2] = params.wValue & 0xff;
setupPacket[3] = (params.wValue >> 8) & 0xff;
setupPacket[4] = params.wIndex & 0xff;
setupPacket[5] = (params.wIndex >> 8) & 0xff;
setupPacket[6] = params.wLength & 0xff;
setupPacket[7] = (params.wLength >> 8) & 0xff;
return setupPacket;
}
export function setDeviceAddressPacket(address) {
return createSetupPacket({
dataDirection: DataDirection.HostToDevice,
type: SetupType.Standard,
recipient: SetupRecipient.Device,
bRequest: SetupRequest.SetAddress,
wValue: address,
wIndex: 0,
wLength: 0,
});
}
export function getDescriptorPacket(type, length, index = 0) {
return createSetupPacket({
dataDirection: DataDirection.DeviceToHost,
type: SetupType.Standard,
recipient: SetupRecipient.Device,
bRequest: SetupRequest.GetDescriptor,
wValue: type << 8,
wIndex: index,
wLength: length,
});
}
export function setDeviceConfigurationPacket(configurationNumber) {
return createSetupPacket({
dataDirection: DataDirection.HostToDevice,
type: SetupType.Standard,
recipient: SetupRecipient.Device,
bRequest: SetupRequest.SetDeviceConfiguration,
wValue: configurationNumber,
wIndex: 0,
wLength: 0,
});
}
+29
View File
@@ -0,0 +1,29 @@
/**
* Interface for simulated USB devices that can be connected to the RP2040 USB host controller.
*/
// USB Setup packet fields - bmRequestType bit masks
// Direction (bit 7)
export const USB_DIR_OUT = 0x00;
export const USB_DIR_IN = 0x80;
// Type (bits 6:5)
export const USB_TYPE_STANDARD = 0x00;
export const USB_TYPE_CLASS = 0x20;
export const USB_TYPE_VENDOR = 0x40;
// Recipient (bits 4:0)
export const USB_RECIP_DEVICE = 0x00;
export const USB_RECIP_INTERFACE = 0x01;
export const USB_RECIP_ENDPOINT = 0x02;
export const USB_RECIP_OTHER = 0x03;
export function parseSetupPacket(setup) {
return {
bmRequestType: setup[0],
bRequest: setup[1],
wValue: setup[2] | (setup[3] << 8),
wIndex: setup[4] | (setup[5] << 8),
wLength: setup[6] | (setup[7] << 8),
// Helpers
direction: setup[0] & 0x80 ? 'in' : 'out',
type: (setup[0] >> 5) & 0x03, // 0=Standard, 1=Class, 2=Vendor
recipient: setup[0] & 0x1f, // 0=Device, 1=Interface, 2=Endpoint, 3=Other
};
}
+245
View File
@@ -0,0 +1,245 @@
export function opcodeADCS(Rdn, Rm) {
return (0b0100000101 << 6) | ((Rm & 7) << 3) | (Rdn & 7);
}
export function opcodeADDS1(Rd, Rn, imm3) {
return (0b0001110 << 9) | ((imm3 & 0x7) << 6) | ((Rn & 7) << 3) | (Rd & 7);
}
export function opcodeADDS2(Rdn, imm8) {
return (0b00110 << 11) | ((Rdn & 7) << 8) | (imm8 & 0xff);
}
export function opcodeADDspPlusImm(Rd, imm8) {
return (0b10101 << 11) | ((Rd & 7) << 8) | ((imm8 >> 2) & 0xff);
}
export function opcodeADDsp2(imm) {
return (0b101100000 << 7) | ((imm >> 2) & 0x7f);
}
export function opcodeADDSreg(Rd, Rn, Rm) {
return (0b0001100 << 9) | ((Rm & 0x7) << 6) | ((Rn & 7) << 3) | (Rd & 7);
}
export function opcodeADDreg(Rdn, Rm) {
return (0b01000100 << 8) | ((Rdn & 0x8) << 4) | ((Rm & 0xf) << 3) | (Rdn & 0x7);
}
export function opcodeADR(Rd, imm8) {
return (0b10100 << 11) | ((Rd & 7) << 8) | ((imm8 >> 2) & 0xff);
}
export function opcodeANDS(Rn, Rm) {
return (0b0100000000 << 6) | ((Rm & 7) << 3) | (Rn & 0x7);
}
export function opcodeASRS(Rd, Rm, imm5) {
return (0b00010 << 11) | ((imm5 & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rd & 0x7);
}
export function opcodeASRSreg(Rdn, Rm) {
return (0b0100000100 << 6) | ((Rm & 0x7) << 3) | ((Rm & 0x7) << 3) | (Rdn & 0x7);
}
export function opcodeBT1(cond, imm8) {
return (0b1101 << 12) | ((cond & 0xf) << 8) | ((imm8 >> 1) & 0x1ff);
}
export function opcodeBT2(imm11) {
return (0b11100 << 11) | ((imm11 >> 1) & 0x7ff);
}
export function opcodeBICS(Rdn, Rm) {
return (0b0100001110 << 6) | ((Rm & 7) << 3) | (Rdn & 7);
}
export function opcodeBL(imm) {
const imm11 = (imm >> 1) & 0x7ff;
const imm10 = (imm >> 12) & 0x3ff;
const s = imm < 0 ? 1 : 0;
const j2 = 1 - (((imm >> 22) & 0x1) ^ s);
const j1 = 1 - (((imm >> 23) & 0x1) ^ s);
const opcode = (0b1101 << 28) | (j1 << 29) | (j2 << 27) | (imm11 << 16) | (0b11110 << 11) | (s << 10) | imm10;
return opcode >>> 0;
}
export function opcodeBLX(Rm) {
return (0b010001111 << 7) | (Rm << 3);
}
export function opcodeBX(Rm) {
return (0b010001110 << 7) | (Rm << 3);
}
export function opcodeCMN(Rn, Rm) {
return (0b0100001011 << 6) | ((Rm & 0x7) << 3) | (Rn & 0x7);
}
export function opcodeCMPimm(Rn, Imm8) {
return (0b00101 << 11) | ((Rn & 0x7) << 8) | (Imm8 & 0xff);
}
export function opcodeCMPregT1(Rn, Rm) {
return (0b0100001010 << 6) | ((Rm & 0x7) << 3) | (Rn & 0x7);
}
export function opcodeCMPregT2(Rn, Rm) {
return (0b01000101 << 8) | (((Rn >> 3) & 0x1) << 7) | ((Rm & 0xf) << 3) | (Rn & 0x7);
}
export function opcodeDMBSY() {
return 0x8f50f3bf;
}
export function opcodeDSBSY() {
return 0x8f4ff3bf;
}
export function opcodeEORS(Rdn, Rm) {
return (0b0100000001 << 6) | ((Rm & 0x7) << 3) | (Rdn & 0x7);
}
export function opcodeISBSY() {
return 0x8f6ff3bf;
}
export function opcodeLDMIA(Rn, registers) {
return (0b11001 << 11) | ((Rn & 0x7) << 8) | (registers & 0xff);
}
export function opcodeLDRreg(Rt, Rn, Rm) {
return (0b0101100 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeLDRimm(Rt, Rn, imm5) {
return (0b01101 << 11) | (((imm5 >> 2) & 0x1f) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeLDRlit(Rt, imm8) {
return (0b01001 << 11) | ((imm8 >> 2) & 0xff) | ((Rt & 0x7) << 8);
}
export function opcodeLDRB(Rt, Rn, imm5) {
return (0b01111 << 11) | ((imm5 & 0x1f) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeLDRsp(Rt, imm8) {
return (0b10011 << 11) | ((Rt & 7) << 8) | ((imm8 >> 2) & 0xff);
}
export function opcodeLDRBreg(Rt, Rn, Rm) {
return (0b0101110 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeLDRH(Rt, Rn, imm5) {
return (0b10001 << 11) | (((imm5 >> 1) & 0xf) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeLDRHreg(Rt, Rn, Rm) {
return (0b0101101 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeLDRSB(Rt, Rn, Rm) {
return (0b0101011 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeLDRSH(Rt, Rn, Rm) {
return (0b0101111 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeLSLSreg(Rdn, Rm) {
return (0b0100000010 << 6) | ((Rm & 0x7) << 3) | (Rdn & 0x7);
}
export function opcodeLSLSimm(Rd, Rm, Imm5) {
return (0b00000 << 11) | ((Imm5 & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rd & 0x7);
}
export function opcodeLSRS(Rd, Rm, imm5) {
return (0b00001 << 11) | ((imm5 & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rd & 0x7);
}
export function opcodeLSRSreg(Rdn, Rm) {
return (0b0100000011 << 6) | ((Rm & 0x7) << 3) | (Rdn & 0x7);
}
export function opcodeMOV(Rd, Rm) {
return (0b01000110 << 8) | ((Rd & 0x8 ? 1 : 0) << 7) | (Rm << 3) | (Rd & 0x7);
}
export function opcodeMOVS(Rd, imm8) {
return (0b00100 << 11) | ((Rd & 0x7) << 8) | (imm8 & 0xff);
}
export function opcodeMOVSreg(Rd, Rm) {
return (0b000000000 << 6) | ((Rm & 0x7) << 3) | (Rd & 0x7);
}
export function opcodeMRS(Rd, specReg) {
return (((0b1000 << 28) | ((Rd & 0xf) << 24) | ((specReg & 0xff) << 16) | 0b1111001111101111) >>> 0);
}
export function opcodeMSR(specReg, Rn) {
return ((0b10001000 << 24) | ((specReg & 0xff) << 16) | (0b111100111000 << 4) | (Rn & 0xf)) >>> 0;
}
export function opcodeMULS(Rn, Rdm) {
return (0b0100001101 << 6) | ((Rn & 7) << 3) | (Rdm & 7);
}
export function opcodeMVNS(Rd, Rm) {
return (0b0100001111 << 6) | ((Rm & 7) << 3) | (Rd & 7);
}
export function opcodeNOP() {
return 0b1011111100000000;
}
export function opcodeORRS(Rn, Rm) {
return (0b0100001100 << 6) | ((Rm & 0x7) << 3) | (Rn & 0x7);
}
export function opcodePOP(P, registerList) {
return (0b1011110 << 9) | ((P ? 1 : 0) << 8) | registerList;
}
export function opcodePUSH(M, registerList) {
return (0b1011010 << 9) | ((M ? 1 : 0) << 8) | registerList;
}
export function opcodeREV(Rd, Rn) {
return (0b1011101000 << 6) | ((Rn & 0x7) << 3) | (Rd & 0x7);
}
export function opcodeREV16(Rd, Rn) {
return (0b1011101001 << 6) | ((Rn & 0x7) << 3) | (Rd & 0x7);
}
export function opcodeREVSH(Rd, Rn) {
return (0b1011101011 << 6) | ((Rn & 0x7) << 3) | (Rd & 0x7);
}
export function opcodeROR(Rdn, Rm) {
return (0b0100000111 << 6) | ((Rm & 0x7) << 3) | (Rdn & 0x7);
}
export function opcodeRSBS(Rd, Rn) {
return (0b0100001001 << 6) | ((Rn & 0x7) << 3) | (Rd & 0x7);
}
export function opcodeSBCS(Rn, Rm) {
return (0b0100000110 << 6) | ((Rm & 0x7) << 3) | (Rn & 0x7);
}
export function opcodeSTMIA(Rn, registers) {
return (0b11000 << 11) | ((Rn & 0x7) << 8) | (registers & 0xff);
}
export function opcodeSTR(Rt, Rm, imm5) {
return (0b01100 << 11) | (((imm5 >> 2) & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeSTRsp(Rt, imm8) {
return (0b10010 << 11) | ((Rt & 7) << 8) | ((imm8 >> 2) & 0xff);
}
export function opcodeSTRreg(Rt, Rn, Rm) {
return (0b0101000 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeSTRB(Rt, Rm, imm5) {
return (0b01110 << 11) | ((imm5 & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeSTRBreg(Rt, Rn, Rm) {
return (0b0101010 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeSTRH(Rt, Rm, imm5) {
return (0b10000 << 11) | (((imm5 >> 1) & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeSTRHreg(Rt, Rn, Rm) {
return (0b0101001 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7);
}
export function opcodeSUBS1(Rd, Rn, imm3) {
return (0b0001111 << 9) | ((imm3 & 0x7) << 6) | ((Rn & 7) << 3) | (Rd & 7);
}
export function opcodeSUBS2(Rdn, imm8) {
return (0b00111 << 11) | ((Rdn & 7) << 8) | (imm8 & 0xff);
}
export function opcodeSUBSreg(Rd, Rn, Rm) {
return (0b0001101 << 9) | ((Rm & 0x7) << 6) | ((Rn & 7) << 3) | (Rd & 7);
}
export function opcodeSUBsp(imm) {
return (0b101100001 << 7) | ((imm >> 2) & 0x7f);
}
export function opcodeSVC(imm8) {
return (0b11011111 << 8) | (imm8 & 0xff);
}
export function opcodeSXTB(Rd, Rm) {
return (0b1011001001 << 6) | ((Rm & 7) << 3) | (Rd & 7);
}
export function opcodeSXTH(Rd, Rm) {
return (0b1011001000 << 6) | ((Rm & 7) << 3) | (Rd & 7);
}
export function opcodeTST(Rm, Rn) {
return (0b0100001000 << 6) | ((Rn & 7) << 3) | (Rm & 7);
}
export function opcodeUXTB(Rd, Rm) {
return (0b1011001011 << 6) | ((Rm & 7) << 3) | (Rd & 7);
}
export function opcodeUDF(imm8) {
return ((0b11011110 << 8) | (imm8 & 0xff)) >>> 0;
}
export function opcodeUDF2(imm16) {
const imm12 = imm16 & 0xfff;
const imm4 = (imm16 >> 12) & 0xf;
return ((0b111101111111 << 4) | imm4 | (0b1010 << 28) | (imm12 << 16)) >>> 0;
}
export function opcodeUXTH(Rd, Rm) {
return (0b1011001010 << 6) | ((Rm & 7) << 3) | (Rd & 7);
}
export function opcodeWFI() {
return 0b1011111100110000;
}
export function opcodeYIELD() {
return 0b1011111100010000;
}
+9
View File
@@ -0,0 +1,9 @@
export function bit(n) {
return 1 << n;
}
export function s32(n) {
return n | 0;
}
export function u32(n) {
return n >>> 0;
}
+52
View File
@@ -0,0 +1,52 @@
export class FIFO {
constructor(size) {
this.start = 0;
this.used = 0;
this.buffer = new Uint32Array(size);
}
get size() {
return this.buffer.length;
}
get itemCount() {
return this.used;
}
push(value) {
const { length } = this.buffer;
const { start, used } = this;
if (this.used < length) {
this.buffer[(start + used) % length] = value;
this.used++;
}
}
pull() {
const { start, used } = this;
const { length } = this.buffer;
if (used) {
this.start = (start + 1) % length;
this.used--;
return this.buffer[start];
}
return 0;
}
peek() {
return this.used ? this.buffer[this.start] : 0;
}
reset() {
this.used = 0;
}
get empty() {
return this.used == 0;
}
get full() {
return this.used === this.buffer.length;
}
get items() {
const { start, used, buffer } = this;
const { length } = buffer;
const result = [];
for (let i = 0; i < used; i++) {
result[i] = buffer[(start + i) % length];
}
return result;
}
}
+44
View File
@@ -0,0 +1,44 @@
import { formatTime } from './time.js';
export var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["Debug"] = 0] = "Debug";
LogLevel[LogLevel["Info"] = 1] = "Info";
LogLevel[LogLevel["Warn"] = 2] = "Warn";
LogLevel[LogLevel["Error"] = 3] = "Error";
})(LogLevel || (LogLevel = {}));
export class ConsoleLogger {
constructor(currentLogLevel, throwOnError = true) {
this.currentLogLevel = currentLogLevel;
this.throwOnError = throwOnError;
}
aboveLogLevel(logLevel) {
return logLevel >= this.currentLogLevel ? true : false;
}
formatMessage(componentName, message) {
const currentTime = formatTime(new Date());
return `${currentTime} [${componentName}] ${message}`;
}
debug(componetName, message) {
if (this.aboveLogLevel(LogLevel.Debug)) {
console.debug(this.formatMessage(componetName, message));
}
}
warn(componetName, message) {
if (this.aboveLogLevel(LogLevel.Warn)) {
console.warn(this.formatMessage(componetName, message));
}
}
error(componentName, message) {
if (this.aboveLogLevel(LogLevel.Error)) {
console.error(this.formatMessage(componentName, message));
if (this.throwOnError) {
throw new Error(`[${componentName}] ${message}`);
}
}
}
info(componentName, message) {
if (this.aboveLogLevel(LogLevel.Info)) {
console.info(this.formatMessage(componentName, message));
}
}
}
+75
View File
@@ -0,0 +1,75 @@
export const PIO_SRC_PINS = 0;
export const PIO_SRC_X = 1;
export const PIO_SRC_Y = 2;
export const PIO_SRC_NULL = 3;
export const PIO_SRC_STATUS = 5;
export const PIO_SRC_ISR = 6;
export const PIO_SRC_OSR = 7;
export const PIO_DEST_PINS = 0;
export const PIO_DEST_X = 1;
export const PIO_DEST_Y = 2;
export const PIO_DEST_NULL = 3;
export const PIO_DEST_PINDIRS = 4;
export const PIO_DEST_PC = 5;
export const PIO_DEST_ISR = 6;
export const PIO_DEST_EXEC = 7;
export const PIO_MOV_DEST_PINS = 0;
export const PIO_MOV_DEST_X = 1;
export const PIO_MOV_DEST_Y = 2;
export const PIO_MOV_DEST_EXEC = 4;
export const PIO_MOV_DEST_PC = 5;
export const PIO_MOV_DEST_ISR = 6;
export const PIO_MOV_DEST_OSR = 7;
export const PIO_OP_NONE = 0;
export const PIO_OP_INVERT = 1;
export const PIO_OP_BITREV = 2;
export const PIO_WAIT_SRC_GPIO = 0;
export const PIO_WAIT_SRC_PIN = 1;
export const PIO_WAIT_SRC_IRQ = 2;
export const PIO_COND_ALWAYS = 0;
export const PIO_COND_NOTX = 1;
export const PIO_COND_XDEC = 2;
export const PIO_COND_NOTY = 3;
export const PIO_COND_YDEC = 4;
export const PIO_COND_XNEY = 5;
export const PIO_COND_PIN = 6;
export const PIO_COND_NOTEMPTYOSR = 7;
export function pioJMP(cond = 0, address, delay = 0) {
return ((delay & 0x1f) << 8) | ((cond & 0x7) << 5) | (address & 0x1f);
}
export function pioWAIT(polarity, src, index, delay = 0) {
return ((1 << 13) |
((delay & 0x1f) << 8) |
((polarity ? 1 : 0) << 7) |
((src & 0x3) << 5) |
(index & 0x1f));
}
export function pioIN(src, bitCount, delay = 0) {
return (2 << 13) | ((delay & 0x1f) << 8) | ((src & 0x7) << 5) | (bitCount & 0x1f);
}
export function pioOUT(Dest, bitCount, delay = 0) {
return (3 << 13) | ((delay & 0x1f) << 8) | ((Dest & 0x7) << 5) | (bitCount & 0x1f);
}
export function pioPUSH(ifFull, noBlock, delay = 0) {
return (4 << 13) | ((delay & 0x1f) << 8) | ((ifFull ? 1 : 0) << 6) | ((noBlock ? 1 : 0) << 5);
}
export function pioPULL(ifEmpty, noBlock, delay = 0) {
return ((4 << 13) |
((delay & 0x1f) << 8) |
(1 << 7) |
((ifEmpty ? 1 : 0) << 6) |
((noBlock ? 1 : 0) << 5));
}
export function pioMOV(dest, op = 0, src, delay = 0) {
return (5 << 13) | ((delay & 0x1f) << 8) | ((dest & 0x7) << 5) | ((op & 0x3) << 3) | (src & 0x7);
}
export function pioIRQ(clear, wait, index, delay = 0) {
return ((6 << 13) |
((delay & 0x1f) << 8) |
((clear ? 1 : 0) << 6) |
((wait ? 1 : 0) << 5) |
(index & 0x1f));
}
export function pioSET(dest, data, delay = 0) {
return (7 << 13) | ((delay & 0x1f) << 8) | ((dest & 0x7) << 5) | (data & 0x1f);
}
+27
View File
@@ -0,0 +1,27 @@
export function getCurrentMicroseconds() {
if (typeof performance != 'undefined') {
return Math.floor(performance.now() * 1000);
}
else {
return Math.floor(eval('require')('perf_hooks').performance.now() * 1000);
}
}
function leftPad(value, minLength, padChar = ' ') {
if (value.length < minLength) {
value = padChar + value;
}
return value;
}
function rightPad(value, minLength, padChar = ' ') {
if (value.length < minLength) {
value += padChar;
}
return value;
}
export function formatTime(date) {
const hours = leftPad(date.getHours().toString(), 2, '0');
const minutes = leftPad(date.getMinutes().toString(), 2, '0');
const seconds = leftPad(date.getSeconds().toString(), 2, '0');
const milliseconds = rightPad(date.getMilliseconds().toString(), 3);
return `${hours}:${minutes}:${seconds}.${milliseconds}`;
}
+201
View File
@@ -0,0 +1,201 @@
export var TimerMode;
(function (TimerMode) {
TimerMode[TimerMode["Increment"] = 0] = "Increment";
TimerMode[TimerMode["Decrement"] = 1] = "Decrement";
TimerMode[TimerMode["ZigZag"] = 2] = "ZigZag";
})(TimerMode || (TimerMode = {}));
export class Timer32 {
constructor(clock, baseFreq) {
this.clock = clock;
this.baseFreq = baseFreq;
this.baseValue = 0;
this.baseNanos = 0;
this.topValue = 0xffffffff;
this.prescalerValue = 1;
this.timerMode = TimerMode.Increment;
this.enabled = true;
this.listeners = [];
}
reset() {
this.baseNanos = this.clock.nanos;
this.baseValue = 0;
this.updated();
}
set(value, zigZagDown = false) {
this.baseValue = zigZagDown ? this.topValue * 2 - value : value;
this.baseNanos = this.clock.nanos;
this.updated();
}
/**
* Advances the counter by the given amount. Note that this will
* decrease the counter if the timer is running in Decrement mode.
*
* @param delta The value to add to the counter. Can be negative.
*/
advance(delta) {
this.baseValue += delta;
}
get rawCounter() {
const { baseFreq, prescalerValue, baseNanos, baseValue, enabled, timerMode } = this;
if (!baseFreq || !prescalerValue || !enabled) {
return this.baseValue;
}
const zigzag = timerMode == TimerMode.ZigZag;
const ticks = ((this.clock.nanos - baseNanos) / 1e9) * (baseFreq / prescalerValue);
const topModulo = zigzag ? this.topValue * 2 : this.topValue + 1;
const delta = timerMode == TimerMode.Decrement ? topModulo - (ticks % topModulo) : ticks;
let currentValue = Math.round(baseValue + delta);
if (this.topValue != 0xffffffff) {
currentValue %= topModulo;
}
return currentValue;
}
get counter() {
let currentValue = this.rawCounter;
if (this.timerMode == TimerMode.ZigZag && currentValue > this.topValue) {
currentValue = this.topValue * 2 - currentValue;
}
return currentValue >>> 0;
}
get top() {
return this.topValue;
}
set top(value) {
const { counter } = this;
this.topValue = value;
this.set(counter <= this.topValue ? counter : 0);
}
get frequency() {
return this.baseFreq;
}
set frequency(value) {
this.baseValue = this.counter;
this.baseNanos = this.clock.nanos;
this.baseFreq = value;
this.updated();
}
get prescaler() {
return this.prescalerValue;
}
set prescaler(value) {
this.baseValue = this.counter;
this.baseNanos = this.clock.nanos;
this.enabled = this.prescalerValue !== 0;
this.prescalerValue = value;
this.updated();
}
toNanos(cycles) {
const { baseFreq, prescalerValue } = this;
return (cycles * 1e9) / (baseFreq / prescalerValue);
}
get enable() {
return this.enabled;
}
set enable(value) {
if (value !== this.enabled) {
if (value) {
this.baseNanos = this.clock.nanos;
}
else {
this.baseValue = this.counter;
}
this.enabled = value;
this.updated();
}
}
get mode() {
return this.timerMode;
}
set mode(value) {
if (this.timerMode !== value) {
const { counter } = this;
this.timerMode = value;
this.set(counter);
}
}
updated() {
for (const listener of this.listeners) {
listener();
}
}
}
export class Timer32PeriodicAlarm {
constructor(timer, callback) {
this.timer = timer;
this.callback = callback;
this.targetValue = 0;
this.enabled = false;
this.handleAlarm = () => {
this.callback();
if (this.enabled && this.timer.enable) {
this.schedule();
}
};
this.update = () => {
this.cancel();
if (this.enabled && this.timer.enable) {
this.schedule();
}
};
this.clockAlarm = this.timer.clock.createAlarm(this.handleAlarm);
timer.listeners.push(this.update);
}
get enable() {
return this.enabled;
}
set enable(value) {
if (value !== this.enabled) {
this.enabled = value;
if (value && this.timer.enable) {
this.schedule();
}
else {
this.cancel();
}
}
}
get target() {
return this.targetValue;
}
set target(value) {
if (value === this.targetValue) {
return;
}
this.targetValue = value;
if (this.enabled && this.timer.enable) {
this.cancel();
this.schedule();
}
}
schedule() {
const { timer, targetValue } = this;
const { top, mode, rawCounter } = timer;
let cycleDelta = targetValue - rawCounter;
if (mode === TimerMode.ZigZag && cycleDelta < 0) {
if (cycleDelta < -top) {
cycleDelta += 2 * top;
}
else {
cycleDelta = top * 2 - targetValue - rawCounter;
}
}
if (top != 0xffffffff) {
if (cycleDelta <= 0) {
cycleDelta += top + 1;
}
if (targetValue > top) {
// Skip alarm
return;
}
}
if (mode === TimerMode.Decrement) {
cycleDelta = top + 1 - cycleDelta;
}
const cyclesToAlarm = cycleDelta >>> 0;
const nanosToAlarm = timer.toNanos(cyclesToAlarm);
this.clockAlarm.schedule(nanosToAlarm);
}
cancel() {
this.clockAlarm.cancel();
}
}
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/*!-----------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
* Released under the MIT license
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
*-----------------------------------------------------------------------------*/
define("vs/basic-languages/apex/apex", ["require","require"],(require)=>{
"use strict";var moduleExports=(()=>{var i=Object.defineProperty;var r=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var l=Object.prototype.hasOwnProperty;var d=(e,t)=>{for(var s in t)i(e,s,{get:t[s],enumerable:!0})},g=(e,t,s,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of c(t))!l.call(e,o)&&o!==s&&i(e,o,{get:()=>t[o],enumerable:!(a=r(t,o))||a.enumerable});return e};var p=e=>g(i({},"__esModule",{value:!0}),e);var h={};d(h,{conf:()=>m,language:()=>b});var m={wordPattern:/(-?\d*\.\d\w*)|([^\`\~\!\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,comments:{lineComment:"//",blockComment:["/*","*/"]},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"},{open:"<",close:">"}],folding:{markers:{start:new RegExp("^\\s*//\\s*(?:(?:#?region\\b)|(?:<editor-fold\\b))"),end:new RegExp("^\\s*//\\s*(?:(?:#?endregion\\b)|(?:</editor-fold>))")}}},u=["abstract","activate","and","any","array","as","asc","assert","autonomous","begin","bigdecimal","blob","boolean","break","bulk","by","case","cast","catch","char","class","collect","commit","const","continue","convertcurrency","decimal","default","delete","desc","do","double","else","end","enum","exception","exit","export","extends","false","final","finally","float","for","from","future","get","global","goto","group","having","hint","if","implements","import","in","inner","insert","instanceof","int","interface","into","join","last_90_days","last_month","last_n_days","last_week","like","limit","list","long","loop","map","merge","native","new","next_90_days","next_month","next_n_days","next_week","not","null","nulls","number","object","of","on","or","outer","override","package","parallel","pragma","private","protected","public","retrieve","return","returning","rollback","savepoint","search","select","set","short","sort","stat","static","strictfp","super","switch","synchronized","system","testmethod","then","this","this_month","this_week","throw","throws","today","tolabel","tomorrow","transaction","transient","trigger","true","try","type","undelete","update","upsert","using","virtual","void","volatile","webservice","when","where","while","yesterday"],f=e=>e.charAt(0).toUpperCase()+e.substr(1),n=[];u.forEach(e=>{n.push(e),n.push(e.toUpperCase()),n.push(f(e))});var b={defaultToken:"",tokenPostfix:".apex",keywords:n,operators:["=",">","<","!","~","?",":","==","<=",">=","!=","&&","||","++","--","+","-","*","/","&","|","^","%","<<",">>",">>>","+=","-=","*=","/=","&=","|=","^=","%=","<<=",">>=",">>>="],symbols:/[=><!~?:&|+\-*\/\^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,digits:/\d+(_+\d+)*/,octaldigits:/[0-7]+(_+[0-7]+)*/,binarydigits:/[0-1]+(_+[0-1]+)*/,hexdigits:/[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/,tokenizer:{root:[[/[a-z_$][\w$]*/,{cases:{"@keywords":{token:"keyword.$0"},"@default":"identifier"}}],[/[A-Z][\w\$]*/,{cases:{"@keywords":{token:"keyword.$0"},"@default":"type.identifier"}}],{include:"@whitespace"},[/[{}()\[\]]/,"@brackets"],[/[<>](?!@symbols)/,"@brackets"],[/@symbols/,{cases:{"@operators":"delimiter","@default":""}}],[/@\s*[a-zA-Z_\$][\w\$]*/,"annotation"],[/(@digits)[eE]([\-+]?(@digits))?[fFdD]?/,"number.float"],[/(@digits)\.(@digits)([eE][\-+]?(@digits))?[fFdD]?/,"number.float"],[/(@digits)[fFdD]/,"number.float"],[/(@digits)[lL]?/,"number"],[/[;,.]/,"delimiter"],[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/"/,"string",'@string."'],[/'/,"string","@string.'"],[/'[^\\']'/,"string"],[/(')(@escapes)(')/,["string","string.escape","string"]],[/'/,"string.invalid"]],whitespace:[[/[ \t\r\n]+/,""],[/\/\*\*(?!\/)/,"comment.doc","@apexdoc"],[/\/\*/,"comment","@comment"],[/\/\/.*$/,"comment"]],comment:[[/[^\/*]+/,"comment"],[/\*\//,"comment","@pop"],[/[\/*]/,"comment"]],apexdoc:[[/[^\/*]+/,"comment.doc"],[/\*\//,"comment.doc","@pop"],[/[\/*]/,"comment.doc"]],string:[[/[^\\"']+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/["']/,{cases:{"$#==$S2":{token:"string",next:"@pop"},"@default":"string"}}]]}};return p(h);})();
return moduleExports;
});
+10
View File
@@ -0,0 +1,10 @@
/*!-----------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
* Released under the MIT license
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
*-----------------------------------------------------------------------------*/
define("vs/basic-languages/azcli/azcli", ["require","require"],(require)=>{
"use strict";var moduleExports=(()=>{var s=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var r=Object.getOwnPropertyNames;var l=Object.prototype.hasOwnProperty;var c=(t,e)=>{for(var o in e)s(t,o,{get:e[o],enumerable:!0})},k=(t,e,o,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of r(e))!l.call(t,n)&&n!==o&&s(t,n,{get:()=>e[n],enumerable:!(a=i(e,n))||a.enumerable});return t};var p=t=>k(s({},"__esModule",{value:!0}),t);var d={};c(d,{conf:()=>f,language:()=>g});var f={comments:{lineComment:"#"}},g={defaultToken:"keyword",ignoreCase:!0,tokenPostfix:".azcli",str:/[^#\s]/,tokenizer:{root:[{include:"@comment"},[/\s-+@str*\s*/,{cases:{"@eos":{token:"key.identifier",next:"@popall"},"@default":{token:"key.identifier",next:"@type"}}}],[/^-+@str*\s*/,{cases:{"@eos":{token:"key.identifier",next:"@popall"},"@default":{token:"key.identifier",next:"@type"}}}]],type:[{include:"@comment"},[/-+@str*\s*/,{cases:{"@eos":{token:"key.identifier",next:"@popall"},"@default":"key.identifier"}}],[/@str+\s*/,{cases:{"@eos":{token:"string",next:"@popall"},"@default":"string"}}]],comment:[[/#.*$/,{cases:{"@eos":{token:"comment",next:"@popall"}}}]]}};return p(d);})();
return moduleExports;
});
+10
View File
@@ -0,0 +1,10 @@
/*!-----------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
* Released under the MIT license
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
*-----------------------------------------------------------------------------*/
define("vs/basic-languages/bat/bat", ["require","require"],(require)=>{
"use strict";var moduleExports=(()=>{var n=Object.defineProperty;var r=Object.getOwnPropertyDescriptor;var l=Object.getOwnPropertyNames;var i=Object.prototype.hasOwnProperty;var g=(o,e)=>{for(var t in e)n(o,t,{get:e[t],enumerable:!0})},c=(o,e,t,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of l(e))!i.call(o,s)&&s!==t&&n(o,s,{get:()=>e[s],enumerable:!(a=r(e,s))||a.enumerable});return o};var p=o=>c(n({},"__esModule",{value:!0}),o);var k={};g(k,{conf:()=>d,language:()=>m});var d={comments:{lineComment:"REM"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}],surroundingPairs:[{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}],folding:{markers:{start:new RegExp("^\\s*(::\\s*|REM\\s+)#region"),end:new RegExp("^\\s*(::\\s*|REM\\s+)#endregion")}}},m={defaultToken:"",ignoreCase:!0,tokenPostfix:".bat",brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.parenthesis",open:"(",close:")"},{token:"delimiter.square",open:"[",close:"]"}],keywords:/call|defined|echo|errorlevel|exist|for|goto|if|pause|set|shift|start|title|not|pushd|popd/,symbols:/[=><!~?&|+\-*\/\^;\.,]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/^(\s*)(rem(?:\s.*|))$/,["","comment"]],[/(\@?)(@keywords)(?!\w)/,[{token:"keyword"},{token:"keyword.$2"}]],[/[ \t\r\n]+/,""],[/setlocal(?!\w)/,"keyword.tag-setlocal"],[/endlocal(?!\w)/,"keyword.tag-setlocal"],[/[a-zA-Z_]\w*/,""],[/:\w*/,"metatag"],[/%[^%]+%/,"variable"],[/%%[\w]+(?!\w)/,"variable"],[/[{}()\[\]]/,"@brackets"],[/@symbols/,"delimiter"],[/\d*\.\d+([eE][\-+]?\d+)?/,"number.float"],[/0[xX][0-9a-fA-F_]*[0-9a-fA-F]/,"number.hex"],[/\d+/,"number"],[/[;,.]/,"delimiter"],[/"/,"string",'@string."'],[/'/,"string","@string.'"]],string:[[/[^\\"'%]+/,{cases:{"@eos":{token:"string",next:"@popall"},"@default":"string"}}],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/%[\w ]+%/,"variable"],[/%%[\w]+(?!\w)/,"variable"],[/["']/,{cases:{"$#==$S2":{token:"string",next:"@pop"},"@default":"string"}}],[/$/,"string","@popall"]]}};return p(k);})();
return moduleExports;
});
+11
View File
@@ -0,0 +1,11 @@
/*!-----------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
* Released under the MIT license
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
*-----------------------------------------------------------------------------*/
define("vs/basic-languages/bicep/bicep", ["require","require"],(require)=>{
"use strict";var moduleExports=(()=>{var r=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var a=Object.prototype.hasOwnProperty;var g=(e,n)=>{for(var o in n)r(e,o,{get:n[o],enumerable:!0})},l=(e,n,o,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let t of c(n))!a.call(e,t)&&t!==o&&r(e,t,{get:()=>n[t],enumerable:!(i=s(n,t))||i.enumerable});return e};var m=e=>l(r({},"__esModule",{value:!0}),e);var y={};g(y,{conf:()=>$,language:()=>w});var p=e=>`\\b${e}\\b`,k="[_a-zA-Z]",x="[_a-zA-Z0-9]",u=p(`${k}${x}*`),d=["targetScope","resource","module","param","var","output","for","in","if","existing"],b=["true","false","null"],f="[ \\t\\r\\n]",C="[0-9]+",$={comments:{lineComment:"//",blockComment:["/*","*/"]},brackets:[["{","}"],["[","]"],["(",")"]],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"'",close:"'"},{open:"'''",close:"'''"}],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"'",close:"'",notIn:["string","comment"]},{open:"'''",close:"'''",notIn:["string","comment"]}],autoCloseBefore:`:.,=}])'
`,indentationRules:{increaseIndentPattern:new RegExp("^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$"),decreaseIndentPattern:new RegExp("^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$")}},w={defaultToken:"",tokenPostfix:".bicep",brackets:[{open:"{",close:"}",token:"delimiter.curly"},{open:"[",close:"]",token:"delimiter.square"},{open:"(",close:")",token:"delimiter.parenthesis"}],symbols:/[=><!~?:&|+\-*/^%]+/,keywords:d,namedLiterals:b,escapes:"\\\\(u{[0-9A-Fa-f]+}|n|r|t|\\\\|'|\\${)",tokenizer:{root:[{include:"@expression"},{include:"@whitespace"}],stringVerbatim:[{regex:"(|'|'')[^']",action:{token:"string"}},{regex:"'''",action:{token:"string.quote",next:"@pop"}}],stringLiteral:[{regex:"\\${",action:{token:"delimiter.bracket",next:"@bracketCounting"}},{regex:"[^\\\\'$]+",action:{token:"string"}},{regex:"@escapes",action:{token:"string.escape"}},{regex:"\\\\.",action:{token:"string.escape.invalid"}},{regex:"'",action:{token:"string",next:"@pop"}}],bracketCounting:[{regex:"{",action:{token:"delimiter.bracket",next:"@bracketCounting"}},{regex:"}",action:{token:"delimiter.bracket",next:"@pop"}},{include:"expression"}],comment:[{regex:"[^\\*]+",action:{token:"comment"}},{regex:"\\*\\/",action:{token:"comment",next:"@pop"}},{regex:"[\\/*]",action:{token:"comment"}}],whitespace:[{regex:f},{regex:"\\/\\*",action:{token:"comment",next:"@comment"}},{regex:"\\/\\/.*$",action:{token:"comment"}}],expression:[{regex:"'''",action:{token:"string.quote",next:"@stringVerbatim"}},{regex:"'",action:{token:"string.quote",next:"@stringLiteral"}},{regex:C,action:{token:"number"}},{regex:u,action:{cases:{"@keywords":{token:"keyword"},"@namedLiterals":{token:"keyword"},"@default":{token:"identifier"}}}}]}};return m(y);})();
return moduleExports;
});
+10
View File
@@ -0,0 +1,10 @@
/*!-----------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
* Released under the MIT license
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
*-----------------------------------------------------------------------------*/
define("vs/basic-languages/cameligo/cameligo", ["require","require"],(require)=>{
"use strict";var moduleExports=(()=>{var s=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var l=Object.prototype.hasOwnProperty;var c=(o,e)=>{for(var n in e)s(o,n,{get:e[n],enumerable:!0})},m=(o,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of a(e))!l.call(o,t)&&t!==n&&s(o,t,{get:()=>e[t],enumerable:!(r=i(e,t))||r.enumerable});return o};var p=o=>m(s({},"__esModule",{value:!0}),o);var u={};c(u,{conf:()=>d,language:()=>g});var d={comments:{lineComment:"//",blockComment:["(*","*)"]},brackets:[["{","}"],["[","]"],["(",")"],["<",">"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'},{open:"(*",close:"*)"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'},{open:"(*",close:"*)"}]},g={defaultToken:"",tokenPostfix:".cameligo",ignoreCase:!0,brackets:[{open:"{",close:"}",token:"delimiter.curly"},{open:"[",close:"]",token:"delimiter.square"},{open:"(",close:")",token:"delimiter.parenthesis"},{open:"<",close:">",token:"delimiter.angle"}],keywords:["abs","assert","block","Bytes","case","Crypto","Current","else","failwith","false","for","fun","if","in","let","let%entry","let%init","List","list","Map","map","match","match%nat","mod","not","operation","Operation","of","record","Set","set","sender","skip","source","String","then","to","true","type","with"],typeKeywords:["int","unit","string","tz","nat","bool"],operators:["=",">","<","<=",">=","<>",":",":=","and","mod","or","+","-","*","/","@","&","^","%","->","<-","&&","||"],symbols:/[=><:@\^&|+\-*\/\^%]+/,tokenizer:{root:[[/[a-zA-Z_][\w]*/,{cases:{"@keywords":{token:"keyword.$0"},"@default":"identifier"}}],{include:"@whitespace"},[/[{}()\[\]]/,"@brackets"],[/[<>](?!@symbols)/,"@brackets"],[/@symbols/,{cases:{"@operators":"delimiter","@default":""}}],[/\d*\.\d+([eE][\-+]?\d+)?/,"number.float"],[/\$[0-9a-fA-F]{1,16}/,"number.hex"],[/\d+/,"number"],[/[;,.]/,"delimiter"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/'/,"string","@string"],[/'[^\\']'/,"string"],[/'/,"string.invalid"],[/\#\d+/,"string"]],comment:[[/[^\(\*]+/,"comment"],[/\*\)/,"comment","@pop"],[/\(\*/,"comment"]],string:[[/[^\\']+/,"string"],[/\\./,"string.escape.invalid"],[/'/,{token:"string.quote",bracket:"@close",next:"@pop"}]],whitespace:[[/[ \t\r\n]+/,"white"],[/\(\*/,"comment","@comment"],[/\/\/.*$/,"comment"]]}};return p(u);})();
return moduleExports;
});
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/*!-----------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
* Released under the MIT license
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
*-----------------------------------------------------------------------------*/
define("vs/basic-languages/coffee/coffee", ["require","require"],(require)=>{
"use strict";var moduleExports=(()=>{var s=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var a=Object.prototype.hasOwnProperty;var l=(n,e)=>{for(var t in e)s(n,t,{get:e[t],enumerable:!0})},p=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of g(e))!a.call(n,r)&&r!==t&&s(n,r,{get:()=>e[r],enumerable:!(o=i(e,r))||o.enumerable});return n};var c=n=>p(s({},"__esModule",{value:!0}),n);var m={};l(m,{conf:()=>d,language:()=>x});var d={wordPattern:/(-?\d*\.\d\w*)|([^\`\~\!\@\#%\^\&\*\(\)\=\$\-\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,comments:{blockComment:["###","###"],lineComment:"#"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],folding:{markers:{start:new RegExp("^\\s*#region\\b"),end:new RegExp("^\\s*#endregion\\b")}}},x={defaultToken:"",ignoreCase:!0,tokenPostfix:".coffee",brackets:[{open:"{",close:"}",token:"delimiter.curly"},{open:"[",close:"]",token:"delimiter.square"},{open:"(",close:")",token:"delimiter.parenthesis"}],regEx:/\/(?!\/\/)(?:[^\/\\]|\\.)*\/[igm]*/,keywords:["and","or","is","isnt","not","on","yes","@","no","off","true","false","null","this","new","delete","typeof","in","instanceof","return","throw","break","continue","debugger","if","else","switch","for","while","do","try","catch","finally","class","extends","super","undefined","then","unless","until","loop","of","by","when"],symbols:/[=><!~?&%|+\-*\/\^\.,\:]+/,escapes:/\\(?:[abfnrtv\\"'$]|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/\@[a-zA-Z_]\w*/,"variable.predefined"],[/[a-zA-Z_]\w*/,{cases:{this:"variable.predefined","@keywords":{token:"keyword.$0"},"@default":""}}],[/[ \t\r\n]+/,""],[/###/,"comment","@comment"],[/#.*$/,"comment"],["///",{token:"regexp",next:"@hereregexp"}],[/^(\s*)(@regEx)/,["","regexp"]],[/(\()(\s*)(@regEx)/,["@brackets","","regexp"]],[/(\,)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\=)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\:)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\[)(\s*)(@regEx)/,["@brackets","","regexp"]],[/(\!)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\&)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\|)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\?)(\s*)(@regEx)/,["delimiter","","regexp"]],[/(\{)(\s*)(@regEx)/,["@brackets","","regexp"]],[/(\;)(\s*)(@regEx)/,["","","regexp"]],[/}/,{cases:{"$S2==interpolatedstring":{token:"string",next:"@pop"},"@default":"@brackets"}}],[/[{}()\[\]]/,"@brackets"],[/@symbols/,"delimiter"],[/\d+[eE]([\-+]?\d+)?/,"number.float"],[/\d+\.\d+([eE][\-+]?\d+)?/,"number.float"],[/0[xX][0-9a-fA-F]+/,"number.hex"],[/0[0-7]+(?!\d)/,"number.octal"],[/\d+/,"number"],[/[,.]/,"delimiter"],[/"""/,"string",'@herestring."""'],[/'''/,"string","@herestring.'''"],[/"/,{cases:{"@eos":"string","@default":{token:"string",next:'@string."'}}}],[/'/,{cases:{"@eos":"string","@default":{token:"string",next:"@string.'"}}}]],string:[[/[^"'\#\\]+/,"string"],[/@escapes/,"string.escape"],[/\./,"string.escape.invalid"],[/\./,"string.escape.invalid"],[/#{/,{cases:{'$S2=="':{token:"string",next:"root.interpolatedstring"},"@default":"string"}}],[/["']/,{cases:{"$#==$S2":{token:"string",next:"@pop"},"@default":"string"}}],[/#/,"string"]],herestring:[[/("""|''')/,{cases:{"$1==$S2":{token:"string",next:"@pop"},"@default":"string"}}],[/[^#\\'"]+/,"string"],[/['"]+/,"string"],[/@escapes/,"string.escape"],[/\./,"string.escape.invalid"],[/#{/,{token:"string.quote",next:"root.interpolatedstring"}],[/#/,"string"]],comment:[[/[^#]+/,"comment"],[/###/,"comment","@pop"],[/#/,"comment"]],hereregexp:[[/[^\\\/#]+/,"regexp"],[/\\./,"regexp"],[/#.*$/,"comment"],["///[igm]*",{token:"regexp",next:"@pop"}],[/\//,"regexp"]]}};return c(m);})();
return moduleExports;
});
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/*!-----------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
* Released under the MIT license
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
*-----------------------------------------------------------------------------*/
define("vs/basic-languages/csharp/csharp", ["require","require"],(require)=>{
"use strict";var moduleExports=(()=>{var s=Object.defineProperty;var r=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var c=Object.prototype.hasOwnProperty;var l=(t,e)=>{for(var o in e)s(t,o,{get:e[o],enumerable:!0})},p=(t,e,o,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of a(e))!c.call(t,n)&&n!==o&&s(t,n,{get:()=>e[n],enumerable:!(i=r(e,n))||i.enumerable});return t};var g=t=>p(s({},"__esModule",{value:!0}),t);var u={};l(u,{conf:()=>d,language:()=>m});var d={wordPattern:/(-?\d*\.\d\w*)|([^\`\~\!\#\$\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,comments:{lineComment:"//",blockComment:["/*","*/"]},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"'",close:"'",notIn:["string","comment"]},{open:'"',close:'"',notIn:["string","comment"]}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],folding:{markers:{start:new RegExp("^\\s*#region\\b"),end:new RegExp("^\\s*#endregion\\b")}}},m={defaultToken:"",tokenPostfix:".cs",brackets:[{open:"{",close:"}",token:"delimiter.curly"},{open:"[",close:"]",token:"delimiter.square"},{open:"(",close:")",token:"delimiter.parenthesis"},{open:"<",close:">",token:"delimiter.angle"}],keywords:["extern","alias","using","bool","decimal","sbyte","byte","short","ushort","int","uint","long","ulong","char","float","double","object","dynamic","string","assembly","is","as","ref","out","this","base","new","typeof","void","checked","unchecked","default","delegate","var","const","if","else","switch","case","while","do","for","foreach","in","break","continue","goto","return","throw","try","catch","finally","lock","yield","from","let","where","join","on","equals","into","orderby","ascending","descending","select","group","by","namespace","partial","class","field","event","method","param","public","protected","internal","private","abstract","sealed","static","struct","readonly","volatile","virtual","override","params","get","set","add","remove","operator","true","false","implicit","explicit","interface","enum","null","async","await","fixed","sizeof","stackalloc","unsafe","nameof","when"],namespaceFollows:["namespace","using"],parenFollows:["if","for","while","switch","foreach","using","catch","when"],operators:["=","??","||","&&","|","^","&","==","!=","<=",">=","<<","+","-","*","/","%","!","~","++","--","+=","-=","*=","/=","%=","&=","|=","^=","<<=",">>=",">>","=>"],symbols:/[=><!~?:&|+\-*\/\^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/\@?[a-zA-Z_]\w*/,{cases:{"@namespaceFollows":{token:"keyword.$0",next:"@namespace"},"@keywords":{token:"keyword.$0",next:"@qualified"},"@default":{token:"identifier",next:"@qualified"}}}],{include:"@whitespace"},[/}/,{cases:{"$S2==interpolatedstring":{token:"string.quote",next:"@pop"},"$S2==litinterpstring":{token:"string.quote",next:"@pop"},"@default":"@brackets"}}],[/[{}()\[\]]/,"@brackets"],[/[<>](?!@symbols)/,"@brackets"],[/@symbols/,{cases:{"@operators":"delimiter","@default":""}}],[/[0-9_]*\.[0-9_]+([eE][\-+]?\d+)?[fFdD]?/,"number.float"],[/0[xX][0-9a-fA-F_]+/,"number.hex"],[/0[bB][01_]+/,"number.hex"],[/[0-9_]+/,"number"],[/[;,.]/,"delimiter"],[/"([^"\\]|\\.)*$/,"string.invalid"],[/"/,{token:"string.quote",next:"@string"}],[/\$\@"/,{token:"string.quote",next:"@litinterpstring"}],[/\@"/,{token:"string.quote",next:"@litstring"}],[/\$"/,{token:"string.quote",next:"@interpolatedstring"}],[/'[^\\']'/,"string"],[/(')(@escapes)(')/,["string","string.escape","string"]],[/'/,"string.invalid"]],qualified:[[/[a-zA-Z_][\w]*/,{cases:{"@keywords":{token:"keyword.$0"},"@default":"identifier"}}],[/\./,"delimiter"],["","","@pop"]],namespace:[{include:"@whitespace"},[/[A-Z]\w*/,"namespace"],[/[\.=]/,"delimiter"],["","","@pop"]],comment:[[/[^\/*]+/,"comment"],["\\*/","comment","@pop"],[/[\/*]/,"comment"]],string:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,{token:"string.quote",next:"@pop"}]],litstring:[[/[^"]+/,"string"],[/""/,"string.escape"],[/"/,{token:"string.quote",next:"@pop"}]],litinterpstring:[[/[^"{]+/,"string"],[/""/,"string.escape"],[/{{/,"string.escape"],[/}}/,"string.escape"],[/{/,{token:"string.quote",next:"root.litinterpstring"}],[/"/,{token:"string.quote",next:"@pop"}]],interpolatedstring:[[/[^\\"{]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/{{/,"string.escape"],[/}}/,"string.escape"],[/{/,{token:"string.quote",next:"root.interpolatedstring"}],[/"/,{token:"string.quote",next:"@pop"}]],whitespace:[[/^[ \t\v\f]*#((r)|(load))(?=\s)/,"directive.csx"],[/^[ \t\v\f]*#\w.*$/,"namespace.cpp"],[/[ \t\v\f\r\n]+/,""],[/\/\*/,"comment","@comment"],[/\/\/.*$/,"comment"]]}};return g(u);})();
return moduleExports;
});
+10
View File
@@ -0,0 +1,10 @@
/*!-----------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.52.0(f6dc0eb8fce67e57f6036f4769d92c1666cdf546)
* Released under the MIT license
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
*-----------------------------------------------------------------------------*/
define("vs/basic-languages/csp/csp", ["require","require"],(require)=>{
"use strict";var moduleExports=(()=>{var o=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var u=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var a=(r,t)=>{for(var s in t)o(r,s,{get:t[s],enumerable:!0})},c=(r,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of u(t))!g.call(r,e)&&e!==s&&o(r,e,{get:()=>t[e],enumerable:!(n=i(t,e))||n.enumerable});return r};var q=r=>c(o({},"__esModule",{value:!0}),r);var p={};a(p,{conf:()=>f,language:()=>l});var f={brackets:[],autoClosingPairs:[],surroundingPairs:[]},l={keywords:[],typeKeywords:[],tokenPostfix:".csp",operators:[],symbols:/[=><!~?:&|+\-*\/\^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/child-src/,"string.quote"],[/connect-src/,"string.quote"],[/default-src/,"string.quote"],[/font-src/,"string.quote"],[/frame-src/,"string.quote"],[/img-src/,"string.quote"],[/manifest-src/,"string.quote"],[/media-src/,"string.quote"],[/object-src/,"string.quote"],[/script-src/,"string.quote"],[/style-src/,"string.quote"],[/worker-src/,"string.quote"],[/base-uri/,"string.quote"],[/plugin-types/,"string.quote"],[/sandbox/,"string.quote"],[/disown-opener/,"string.quote"],[/form-action/,"string.quote"],[/frame-ancestors/,"string.quote"],[/report-uri/,"string.quote"],[/report-to/,"string.quote"],[/upgrade-insecure-requests/,"string.quote"],[/block-all-mixed-content/,"string.quote"],[/require-sri-for/,"string.quote"],[/reflected-xss/,"string.quote"],[/referrer/,"string.quote"],[/policy-uri/,"string.quote"],[/'self'/,"string.quote"],[/'unsafe-inline'/,"string.quote"],[/'unsafe-eval'/,"string.quote"],[/'strict-dynamic'/,"string.quote"],[/'unsafe-hashed-attributes'/,"string.quote"]]}};return q(p);})();
return moduleExports;
});

Some files were not shown because too many files have changed in this diff Show More