- USB CDC 경로 복원: #define Serial Serial1 우회 제거 - rp2040js/avr8js/codemirror/xterm/monaco-editor vendor화 - /vendor 정적 라우팅 추가 (Express 5 catch-all 우회) - 부팅 완료 타임아웃 감지 (Serial 출력 없어도 8초 후 RAF 전환) - 리셋 시 Simulator 재생성으로 주변 장치 상태 초기화 - 시리얼 패널의 내부 메시지를 console.log로 이동
342 lines
14 KiB
JavaScript
342 lines
14 KiB
JavaScript
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();
|
|
}
|
|
}
|