Skip to content

Commit

Permalink
dual mcu demo
Browse files Browse the repository at this point in the history
  • Loading branch information
c1570 committed Mar 14, 2023
1 parent df95e5a commit dc2c181
Show file tree
Hide file tree
Showing 9 changed files with 2,840 additions and 0 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ sudo umount fat12/ # unmount the filesystem
While CircuitPython does not typically use a writeable filesystem, note that this functionality is unavailable (see MicroPython filesystem
support section for more details).

### Dual RP2040 demo
```
npm install
npm run start:dual-mcu
```

This fires up two instances of rp2040js including some glue code that connects the GPIOs 2-10 in an open collector/pull up bus fashion.
It will write a VCD trace of bus signals to the file `dual-mcu-bus-trace.vcd` including a virtual signal indicating conflicts on the bus.
The demo programs on the two RP2040s pull a single line low each (MCU0: D0 then D1 then ... then D7, MCU1 in the opposite direction).

## Learn more

- [Live-coding stream playlist](https://www.youtube.com/playlist?list=PLLomdjsHtJTxT-vdJHwa3z62dFXZnzYBm)
Expand Down
120 changes: 120 additions & 0 deletions demo/dual-mcu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as fs from "fs";
import { RP2040 } from "../src";
import { GPIOPinState } from "../src/gpio-pin";
import { bootromB1 } from "./bootrom";
import { loadHex } from "./intelhex";

const hex1 = fs.readFileSync("demo/dual-mcu/dual-mcu-0.hex", "utf-8");
const hex2 = fs.readFileSync("demo/dual-mcu/dual-mcu-1.hex", "utf-8");
const mcu1 = new RP2040();
const mcu2 = new RP2040();
mcu1.loadBootrom(bootromB1);
mcu2.loadBootrom(bootromB1);
loadHex(hex1, mcu1.flash, 0x10000000);
loadHex(hex2, mcu2.flash, 0x10000000);

mcu1.uart[0].onByte = (value) => {
process.stdout.write(new Uint8Array([value]));
};

mcu2.uart[0].onByte = (value) => {
process.stdout.write(new Uint8Array([value]));
};

// GPIOPinState: { Low, High, Input, InputPullUp, InputPullDown }

const pin_state: number[][] = [
[0, 0, 0, 0, 0, 0, 0], // result value
[3, 3, 3, 3, 3, 3, 3], // input from mcu1 (pullup initially)
[3, 3, 3, 3, 3, 3, 3], // input from mcu2 (pullup initially)
];
const pin_gpio: number[] = [2, 3, 4, 5, 6, 7, 8, 9];
const pin_label: string[] = ["d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7"];
const vcd_file = fs.createWriteStream("dual-mcu-bus-trace.vcd", {});
let last_conflict_cycle: number = -1;

// This listener connects the two MCUs and writes a VCD signal trace file.
// This code assumes pullups enabled (open collector/pullup bus).
function pinListener(mcu_id: number, pin: number) {
return (state: GPIOPinState, oldState: GPIOPinState) => {
pin_state[mcu_id + 1][pin] = state;
const v: number =
pin_state[0 + 1][pin] === 0 || pin_state[1 + 1][pin] === 0 ? 0 : 1;
mcu1.gpio[pin_gpio[pin]].setInputValue(v == 1 ? true : false);
mcu2.gpio[pin_gpio[pin]].setInputValue(v == 1 ? true : false);

// write signal to VCD file
const pin_vcd_id = String.fromCharCode(pin + 34);
if (pin_state[0][pin] !== v) {
pin_state[0][pin] = v;
vcd_file.write(`#${mcu1.core.cycles} ${v}${pin_vcd_id}\n`);
}

// write conflict flag to VCD file
const conflict: boolean =
(pin_state[0 + 1][pin] === 0 && pin_state[1 + 1][pin] === 1) ||
(pin_state[0 + 1][pin] === 1 && pin_state[1 + 1][pin] === 0);
if (conflict)
console.log(
`Conflict on pin ${pin_label[pin]} at cycle ${mcu1.core.cycles} (${
pin_state[0 + 1][pin]
}/${pin_state[1 + 1][pin]})`
);
const have_new_conflict = conflict && last_conflict_cycle === -1;
const conflict_recently_resolved = !conflict && last_conflict_cycle !== -1;
if (
conflict_recently_resolved &&
mcu1.core.cycles === last_conflict_cycle
) {
// one mcu set conflict and other resolved in same cycle:
// delay until next signal change so that the conflict signal is visible in VCD
return;
}
const write_conflict_flag: boolean =
have_new_conflict || conflict_recently_resolved;
if (write_conflict_flag) {
vcd_file.write(`#${mcu1.core.cycles} ${conflict ? 1 : 0}!\n`);
}
last_conflict_cycle = conflict ? mcu1.core.cycles : -1;
};
}

for (let i = 0; i < pin_label.length; i++) {
mcu1.gpio[pin_gpio[i]].addListener(pinListener(0, i));
mcu2.gpio[pin_gpio[i]].addListener(pinListener(1, i));
}

mcu1.core.PC = 0x10000000;
mcu2.core.PC = 0x10000000;

// write VCD file header
vcd_file.write("$timescale 1ns $end\n");
vcd_file.write("$scope module logic $end\n");
vcd_file.write(`$var wire 1 ! bus_conflict $end\n`);
for (let pin = 0; pin < pin_label.length; pin++) {
const pin_vcd_id = String.fromCharCode(pin + 34);
vcd_file.write(`$var wire 1 ${pin_vcd_id} ${pin_label[pin]} $end\n`);
}
vcd_file.write("$upscope $end\n");
vcd_file.write("$enddefinitions $end\n");

function run_mcus() {
let cycles_mcu2_behind = 0;
for (let i = 0; i < 100000; i++) {
if (mcu1.core.cycles % (1 << 25) === 0)
console.log(`clock: ${mcu1.core.cycles / 125000000} secs`);
// run mcu1 for one step, take note of how many cycles that took,
// then step mcu2 until it caught up.
let cycles = mcu1.core.cycles;
mcu1.step();
cycles_mcu2_behind += mcu1.core.cycles - cycles;
while (cycles_mcu2_behind > 0) {
cycles = mcu2.core.cycles;
mcu2.step();
cycles_mcu2_behind -= mcu2.core.cycles - cycles;
}
}
setTimeout(() => run_mcus(), 0);
}

run_mcus();
42 changes: 42 additions & 0 deletions demo/dual-mcu/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
cmake_minimum_required(VERSION 3.12)
include(pico_sdk_import.cmake)

project(dual-mcu C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

pico_sdk_init()

add_executable(dual-mcu-0
dual-mcu.c
)

add_executable(dual-mcu-1
dual-mcu.c
)

target_compile_options(dual-mcu-0 PRIVATE -Wall -DMCU0)
target_compile_options(dual-mcu-1 PRIVATE -Wall -DMCU1)

target_link_libraries(dual-mcu-0
hardware_pio
pico_stdlib
pico_util
)

target_link_libraries(dual-mcu-1
hardware_pio
pico_stdlib
pico_util
)

pico_enable_stdio_usb(dual-mcu-0 0)
pico_enable_stdio_uart(dual-mcu-0 1)
pico_enable_stdio_usb(dual-mcu-1 0)
pico_enable_stdio_uart(dual-mcu-1 1)

pico_generate_pio_header(dual-mcu-0 ${CMAKE_CURRENT_LIST_DIR}/dual-mcu.pio)
pico_generate_pio_header(dual-mcu-1 ${CMAKE_CURRENT_LIST_DIR}/dual-mcu.pio)

pico_add_extra_outputs(dual-mcu-0)
pico_add_extra_outputs(dual-mcu-1)
Loading

0 comments on commit dc2c181

Please sign in to comment.