Enterprise Muisc Graphics или история о том как я прикручивал Rust для RISC-V процессора YRV-Plus.
Начало истории
Эта история началось с того, что однажды во время наших воскресных встреч в Zoom Юрий Панчул / Yuri Panchul @YuriPanchul охарактеризовал процессор YRV, как “процессор который стоит где-нибудь в термометре” (за точность цитаты не ручаюсь). Сам кейс мне показался интересным: почему бы не взять DHT-11 и не вывести данные от него через ModBus. Так как у нас ПЛИС, то работу с датчиком можно реализовать на SystemVerilog и получаем достаточно простой учебный проект - берем данные из параллельного порта и передаем в последовательный, оба вида портов в YRV есть.
Но в сборнике лабораторных работ Basic Graphics Music (https://habr.com/ru/articles/762108/) есть более интересный пример - это распознавание нот и мелодии. Почему бы в качестве источника данных не использовать флейту, а в качестве последовательного порта не использовать MIDI порт - это ведь последовательный порт? Тем более, что подобные продукты я использовал для обучения игре на фортепиано. И, так согласовав с Юрием идею, proof of concept проекта получился следующий: звук извлеченный из флейты должен быть отображен нотой в MuseScore 4.
Параллельно другая проблема не давала покоя - компилятор и среда разработки для проекта YRV-Plus. При работе над проектом “Ретро-компьютер уровня «Радио-86РК» с RISC-V процессором на плате OMDAZZ” сразу же возник вопрос - где брать компилятор, тем более, что их два. Для себя я конечно его собрал из исходников, но рекомендовать делать это другим - это чересчур. Брать сборки у других проектов - стыдно. Также необходимо было решить вопрос со средой, должно быть не хуже чем у SiFive, но в проекте кроме C, в режиме “умного паяльника” используется еще и Verilog.
Один из руководителей одной из серьезных компаний обмолвился, что для низкоуровневого программирования они начинают потихоньку использовать Rust. Про Rust я на тот момент ничего не знал, кроме того, что это какая-то крутая штука, да и Линус Торвальдс не против Rust. Быстрое гугление показало, что Rust для RISC-V вроде бы есть, и вроде бы источник один, а ставится все одной командой - то что нужно, ну а со средой как нибудь разберемся, главное что это не C/C++ c Eclipse Embedded CDT.
И так решено: в проекте будет RISC-V ядро, а код будет написан на Rust, а все это в шутку назовем - Enterprise Graphics Music.
Аппаратное обеспечение
Про память
После работы с платой OMDAZZ, мне захотелось побольше памяти.
Для реализации проекта была выбрана плата Terasic DE0-CV, любезно предоставленная FPGA-Systems.ru. Современные ПЛИС содержат достаточное количество логических элементов для синтеза RISC-V ядра микроконтроллерного уровня. Ограниченным ресурсом является блочная память. Установленная в плате DE0-CV ПЛИС Cyclone V содержит достаточное количество встроенной памяти - 3080 Kbits.
При всем интересе к GOWIN, на мой взгляд, для лабораторного домашнего процессоростроения и ретрокомпьютинга где основной лимит ресурсов - это блочная память ПЛИС, отладки с микросхемой Cyclone V - самое удачное вложение средств,а вот для конечных коммерческих изделий, как видно из таблицы, GOWIN возможно будет лучшим вариантом.
Плата |
Чип |
Память (бит) |
DE0-CV |
5CEBA4F23C7 |
3383K |
DE10 |
5CSXFC6D6F31C6N |
5440K |
DE1-SoC |
5CSEMA5F31C6 |
4450K |
DE0-Nano-Soc |
5CSEMA4U23C6 |
2640K |
DE10-Nano |
5CSEBA6U23I7 |
5440K |
Tang Nano 9K |
GW1NR-9 |
468K |
Tang Nano 20K |
GW2AR-18 QN88 |
828K |
Tang Primer 20K |
GW2A-LV18PG256C8/I7 |
828K |
Отдельно необходимо сказать про серию Nano (и пусть Вас не пугает название SoC). Эти платы значительно беднее в части обвеса, что затрудняло выполнения базовых лабораторных работ например по книге “Цифровой синтез. Практический курс”, но зато продаются по более привлекательной цене. В проекте Basic Graphics Music было сделано подключение модуля клавиатуры и светодиодной индикации TM1638, что позволило получить необходимое количество переключателей и семисегментных индикаторов необходимых для выполнения лабораторных работ, а также был подключен VGA 666 модуль. (Подробнее тут https://habr.com/ru/articles/762108/)
Если SDRAM припаяли, значит это кому-то нужно
Пример Станислава Жельнио @SparF по подключению SDRAM к MIPSfpga долгое время не давал покоя (да и сейчас не дает). Но при реализации проекта хотелось соблюсти два условия: первое - проект должен быть синтезируем в “железе”, второе - кэш будет все таки необходим(но совсем не для конвейера).
Конечно хочется увидеть собственный процессор в реале, не на ПЛИС,и тогда конечно же на ум приходит Sky130 PDK, а также Skywater / Efabless. Но после знакомства со статьей “Yosys - A Free Verilog Synthesis Suite” (https://yosyshq.net/yosys/files/yosys-austrochip2013.pdf) становится понятно что для синтенза процессора небходимо всего четыре элемента, например: буфер, NOR, NAND и NOT. Таким образом ядро может быть синтезировано на 74 или 155 серии. И да, в исходниках Yosys есть такой пример https://github.com/YosysHQ/yosys/blob/master/examples/cmos .
Спроектировать SDRAM-контроллер достойный синтеза в железе задача сложная, поэтому SDRAM можно просто оставить для видео адаптера в виде отдельного модуля, и работать с ним через порты ввода-вывода по определенному протоколу, не изменяя архитектуру микроконтроллера, как это например сделано в Марсоходе - https://marsohod.org/projects/mcy112-prj/429-vga-framebuffer-mcy112
В микроконтроллере в качестве RISC-V ядра используется YRV с 16 битной шиной данный, такое ядро в полтора раза медленней по тестам CoreMark по сравнению с 32 битной версией, но зато реально синтезируемо для работы со статической памятью. На первом этапе решено было не менять шину микроконтроллера и использовать 64Кб ОЗУ. По документации на Embedded Rust такого объема должно хватить на начальном этапе.
Последовательные порты
Было решено заменить дизайн последовательного порта от Монте идущего в составе микроконтроллера на дизайн из книги Понг Чу. Монте свой дизайн в книге не объясняет и, как он сам сказал, дизайн приведен исключительно для примера. А в книге Понг Чу кроме объяснения еще есть и полноценный FIFO, что существенно облегчает работу с консолью. В дальнейшем мне стало интересно, как YRV может использовать другие дизайны последовательных портов, и после некоторых экспериментов я остановился на простом варианте от Ben Marshall.
Сигнал TX enable (wr_uart, uart_tx_en) для модуля UART формируется из сигнала HWRITE шины AHB-Lite, в YRV это сигнал mem_write. Сигнал mem_trans[1:0] равный 2’b11 показывает что выполняется инструкция чтения или сохранения.
io_wr_reg <=mem_write && &mem_trans && (mem_addr[31:16] == `IO_BASE);
Производится запись только последнего байта, выбор которого осуществляется сигналом mem_ble
ld_wdata = io_wr_reg && port7_dec && mem_ble_reg[0] && mem_ready;
Вывод данных из последовательного порта, младший байт данные, старший состояние порта:
assign port7_dat = {5'h0, bufr_ovr, bufr_full, bufr_empty, rx_rdata};
MIDI интерфейс не подразумевает вывода по этому там все проще:
assign portMIDI_dat = {7'h0, midi_bufr_full, 8'b0};
Последовательных портов в проекте два: первый - консоль ввода вывода, вторая - MIDI интерфейс. Но ниже будет показано, что специальные параметры в виде странного baud rate 32500 для MIDI интерфейса будут не важны.
Общая архитектура
Так как в проекте загрузка программы осуществляется через последовательный порт, для сокращения хвостов загрузка программы осуществляется через консольный порт, режимы работы порта определяются переключателем.
Модуль распознавания нот был заимствован из лабораторной работы 11_note_recognition Basics graphics music , оформлен отдельным модулем и соединен с параллельным портом YRV.
module note
(
input clk,
input rst,
input [ 23:0] mic,
output [w_note - 1:0] o_note
);
Rust и RISC-V
Сборка и загрузка программы
Для сборки кода на Rust под архитектуру RISC-V необходимо переключится на ночной канал выпуска Rust
$ rustup toolchain install nightly
$ rustup override set nightly
После этого необходимо установить поддержку архитектуры RISC-V. Поддерживаемые архитектуры можно увидеть командой
$ rustc --print target-list
В списке поддерживаемых архитектур нет архитектуры riscv32ic, поэтому на этом этапе остановимся архитектуре riscv32i
$ rustup target add riscv32i-unknown-none-elf
Дальнейшая сборка осуществляется командами
$ cargo build -Z build-std=core --target riscv32i-unknown-none-elf --release
Для изготовление прошивки воспользуемся objcopy из состава GCC и утилитой bin2hex от от SiFive:
$ riscv-none-elf-objcopy.exe -O binary ../target/riscv32i-unknown-none-elf/release/app app.bin
$ python bin2hex/freedom-bin2hex.py -w16 app.bin >code.mem16
В этот раз я решил всю сборку сделать под WIndows, поэтому загрузка прошивки осуществляется скриптом load.bat следующего содержания
rem The port number should be adjusted
set a=6
mode com%a% baud=57600 parity=n data=8 stop=1 to=off xon=off odsr=off octs=off dtr=off rts=off idsr=off
type code.mem16 >\.\COM%a%
Runtime для YRV-Plus
В качестве runtime используется модифицированный крейт riscv-rt (https://github.com/rust-embedded/riscv-rt) достаточно старой версии 0.11.0. В отличии от текущей master ветки, в данной версии стартовый код выполнен в виде отдельного ассемблерного файла asm.S, что позволило обеспечить совместимость с ассемблерным кодом примеров из книги Inside an Open-Source Processor. Конечный стартовый код оформлен в виде файла yrv.S и соответствует системе старта и прерываний процессора YRV. Также был изменен линкер скрипт link.x под вектора прерываний процессора.
Подключение крейта производится стандартно, в файле Cargo.toml проекта указываем зависмость:
[dependencies]
riscv-rt = { path = "../riscv-rt" }
Там же создаем директорию .cargo в которой создаем файл config следующего содержания:
[target.riscv32i-unknown-none-elf]
rustflags = [
"-C", "link-arg=-Tlink.x"
]
[build]
target = "riscv32i-unknown-none-elf"
Так как в RISC-V порты это просто память, то с ними можно работать напрямую в unsafe блоках. Самый просто способ работать не будет, точнее будет, но только один раз:
pub fn tm()-> u16 {
let buffer: &mut u16 = unsafe { &mut *(0xFFFF000A as *mut u16) };
*buffer
}
Для того чтобы это работало необходимо вызвать метод read_volatile()
pub fn get_data_mem()-> u16 {
let addr = 0xFFFF0016u32;
unsafe {
(addr as *mut u16).read_volatile()
}
}
Но мне больше понравился способ работы с портами используя ассемблер, так как анализ кода при помощи objdump показал что компилятор в генерирует такой код:
pub fn get_data()-> u16 {
let mut out_half;
unsafe {
core::arch::asm!(
"lui t0,0xffff0",
"lh {}, 16(t0)",
out(reg) out_half
); }
out_half
}
Запись производится аналогично:
unsafe {
core::arch::asm!(
"lui t0,0xffff0",
"sb {0}, 14(t0)",
in(reg) byte
); }
В отличии от первых двух примеров в данном случае использование unsafe блока хоть как-то оправдано- все таки используем ассемблер, да и работа с портом в этом примере более прозрачна. Необходимый минимум для использования Rust с процессором YRV-Plus получен.
Атомный макрос println!
И так у нас есть последовательный порт, и конечно же жизнь без макроса println! скучна. Тут я обратился к проекту Writing an OS in Rust (https://os.phil-opp.com/), статья VGA Text Mode. И тут выяснилось самое интересное - макрос lazy_static! не реализуем на архитектуре RV32I ввиду отсутствия Atomic операций.
И следующий код из примера не скомпилируется под архитектуру RV32I, так как для реализации Mutex необходима поддержка atomic инструкций
// in src/vga_buffer.rs
use spin::Mutex;
...
lazy_static! {
pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer {
column_position: 0,
color_code: ColorCode::new(Color::Yellow, Color::Black),
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
});
}
Ядро YRV частично поддерживает стандарт “A”, но только в части Atomic Memory Operations, но для работы с Mutex необходимо чтобы присутствовала поддержка Load-Reserved/Store-Conditional инструкций.
Инструкция lr.w rd,rs1 загружает слово по адресу в rs1, помещает расширенное знаком значение в rd и регистрирует резервирование по адресу памяти. Инструкция sc.w rd,rs1,rs2 записывает слово в rs2 по адресу в rs1, при условии, что по этому адресу все еще существует действительная резервация, sc записывает ноль в rd в случае успеха или ненулевой код в случае неудачи. Вот тут то и нужен кэш, эти инструкции реализуются при наличии кэша, в случае же его отсутствия в любом случае необходима какая-то структура сохраняющая результаты резервирования. Так как у нас система однопоточная, то результаты резервирования нам не интересны и всегда могут быть успешными.
Декодер для AMO инструкций в процессоре присутствует, а сами AMO инструкции реализованы на выделенном ALU.
always @ (imm_6_reg or ls_amo_add or ls_amo_reg or ls_data_reg or mem_rdat) begin
case ({ls_amo_reg, imm_6_reg[11:7]})
6'b100000: ls_amo_out = ls_amo_add;
6'b100100: ls_amo_out = ls_data_reg ^ mem_rdat;
6'b101000: ls_amo_out = ls_data_reg | mem_rdat;
6'b101100: ls_amo_out = ls_data_reg & mem_rdat;
default: ls_amo_out = ls_data_reg;
endcase
end
Формат LR/SC и AMO идентичен, и если мы посмотрим описание инструкции AMO, то все они загружают данные в регистр по адресу и сохраняют результат арифметической операции обратно в память. Так как у нас однопоточная система, то мы можем реализовать LR/SC на основе AMO.
lr.w
31-27 |
26 |
25 |
24-20 |
19-15 |
14-12 |
11-7 |
6-2 |
1-0 |
00010 |
aq |
rl |
00000 |
rs1 |
010 |
rd |
01011 |
11 |
amoadd.w
31-27 |
26 |
25 |
24-20 |
19-15 |
14-12 |
11-7 |
6-2 |
1-0 |
00000 |
aq |
rl |
rs2 |
rs1 |
010 |
rd |
01011 |
11 |
Таким образом lr.w rd,rs1 можно представить в виде amoadd.w rd,zero,(rs1) Например если мы модифицируем ALU следующим образом то получим необходимую инструкцию. Недостаток состоит в том что будет производится сохранения в память.
always @ (imm_6_reg or ls_amo_add or ls_amo_reg or ls_data_reg or mem_rdat) begin
case ({ls_amo_reg, imm_6_reg[11:7]})
6'b100000: ls_amo_out = ls_amo_add;
6'b100100: ls_amo_out = ls_data_reg ^ mem_rdat;
6'b101000: ls_amo_out = ls_data_reg | mem_rdat;
6'b101100: ls_amo_out = ls_data_reg & mem_rdat;
6'b100010: ls_amo_out = mem_rdat;
default: ls_amo_out = ls_data_reg;
endcase
end
Если мы посмотрим на ALU для AMO то увидим что, инструкция sc.w является разновидностью amoswap только в регистре должно сохранятся нулевое значение. Основное ALU ядра YRV имеет две команды сквозной передачи регистра, именно так и работает передача результатов AMO. Хотя по умолчанию ALU возвращает ‘0 сделаем отдельный сигнал для ALU если исполняется команда sc.w , то необходимо возвращать 0 для этого добавим регистр a_zero_5_reg
assign amo_4_dec_sc = (opc_4_amo && fn3_4_2 && fn7_4_sc);
a_zero_5_reg <= amo_4_dec_sc;
always @ (...) begin
casex ({a_ext_5_reg, a_tst_5_reg, a_xor_5_reg, a_and_5_reg,
a_or_5_reg, a_add_5_reg, a_bin_5_reg, a_ain_5_reg, a_zero_5_reg}) //synthesis parallel_case
9'bxxxxxxxx1: alu_5_out = 32'h0; /* zero for sc */
9'bxxxxxxx1x: alu_5_out = alu_5_ain; /* pass a */
9'bxxxxxx1xx: alu_5_out = alu_5_bin; /* pass b */
9'bxxxxx1xxx: alu_5_out = alu_5_add; /* add/sub */
9'bxxxx1xxxx: alu_5_out = alu_5_ain | alu_5_bin; /* or */
9'bxxx1xxxxx: alu_5_out = alu_5_ain & alu_5_bin; /* and */
9'bxx1xxxxxx: alu_5_out = alu_5_ain ^ alu_5_bin; /* xor */
9'bx1xxxxxxx: alu_5_out = {31'h0, alu_5_tst}; /* test */
9'b1xxxxxxxx: alu_5_out = alu_5_ext; /* external */
default: alu_5_out = 32'h0;
endcase
end
Нестандартная архитектура
Так как в поддерживаемых архитектурах не архитектуры RC32IA, то для этого необходимо создать свой конфигурационный JSON файл
rustc -Z unstable-options --target=riscv32i-unknown-none-elf --print target-spec-json
В полученный JSON файл необходимо добавить флаг atomic операций "features": "+a", так же необходимо указать что это не типовая архитектура "is-builtin": false.
{
"arch": "riscv32",
"cpu": "generic-rv32",
"data-layout": "e-m:e-p:32:32-i64:64-n32-S128",
"eh-frame-header": false,
"emit-debug-gdb-scripts": false,
"features": "+a",
"is-builtin": false,
"linker": "rust-lld",
"linker-flavor": "ld.lld",
"llvm-target": "riscv32",
"max-atomic-width": 32,
"panic-strategy": "abort",
"relocation-model": "static",
"target-pointer-width": "32"
}
Сборка осуществляется командой с указанием конфигурационного JSON файла.
$ cargo build -Z build-std=core --target riscv32ia-unknown-none-elf.json --release
Пример с lazy_static! собирается , и в результате objdump показывает что в результирующем коде есть инструкции LR/SC. Что и требовалось получить.
Global allocator
Для полного счастья нам не хватает только кучи. Для этого необходимо настроить global allocator. Так как наш процессор теперь поддерживает мьютексы то можно использовать стандартный linked_allocator.
Для этого в линкер скрипт добавим раздел heap
.heap (NOLOAD) :
{
_sheap = .;
. += _heap_size;
. = ALIGN(4);
_eheap = .;
} > BRAM
И добавим allocator
#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();
extern "C" {
static _sheap: u8;
static _heap_size: u8;
}
pub fn init_heap() {
unsafe {
let heap_start = &_sheap as *const u8 as *mut u8;
let heap_size = &_heap_size as *const u8 as usize;
ALLOCATOR.lock().init(heap_start, heap_size);
}
}
Так же необходимо указать чтобы собрался alloc
cargo build -Z build-std=core,alloc --target riscv32ia-unknown-none-elf.json --release
И Ура! у нас собирается следующая конструкция
let mut xs: Vec<u32> = Vec::new();
MIDI
Сама реализация отправки команд MIDI очень проста и не претендует на эталон. Команду MIDI мы сохраним в следующей структуре
pub struct Message {
pub size: usize,
pub command: [u8;3],
}
И инициализируем команды включения - выключения нот:
let note_A_on = Message::new( [0x90,0x45,0x40], 3);
let note_B_on = Message::new( [0x90,0x47,0x40], 3);
...
let note_A_off = Message::new( [0x80,0x45,0x00], 3);
let note_B_off = Message::new( [0x80,0x47,0x00], 3);
И в конструкции match будем включать и выключать ноты (более подробнее в коде)
match note {
4 => midi::send_message(¬e_A_on),
1 => midi::send_message(¬e_B_on),
2048 => midi::send_message(¬e_C_on),
512 => midi::send_message(¬e_D_on),
128 => midi::send_message(¬e_E_on),
64 => midi::send_message(¬e_F_on),
16 => midi::send_message(¬e_G_on),
_ => sleep(1),
};
pub fn write(byte: u8) {
for _ in 0..700 {
unsafe { core::arch::asm!("nop"); }
}
unsafe {
core::arch::asm!(
"lui t0,0xffff0",
"sb {0}, 20(t0)",
in(reg) byte
); }
}
pub fn send_message(message:&Message) {
for n in 0..message.size {
write(message.command[n]);
}
}
На этапе концепции не стал контролировать состояние порта и сделал синхронизацию через NOP.
Основная сложность - это сделать так чтобы MIDI устройство появилось в Windows. Для этого нам необходимо два инструмента loopMIDI (https://www.tobias-erichsen.de/software/loopmidi.html) и Hairless MIDI<->Serial Bridge который мы подключаем в loopMIDI:
Для отладки используем MIDI-OX , и можем увидеть включение - выключения нот
А также получаем работающую консоль
Вывод
Мы модифицировали процессор YRV для того чтобы не иметь проблем с основными примерами на Rust. Как мы увидели поддержка atomic инструкций куда более важна чем поддержка аппаратного умножения или деления. В тоже время если интересует изучение запуска Rust на голом железе, например для написания операционных систем или просто для прохождения курса по алгоритмам и структурам данных на чем-то экзотическом, то нет необходимости покупки микроконтроллера, микроконтроллер может быть синтезирован самостоятельно используя ПЛИС. Более того в этом случае вы можете сконфигурировать любое интересующее вас количество портов, так как ПЛИС в отличие от ASIC отличается развитой разлапистостью.
Ну и конечно видео!
Автор выражает благодарность @YuriPanchul, @KeisN13 , а также команде OTUS по курсу Rust Developer. Professional.
Комментарии (12)
checkpoint
07.01.2024 22:20Добрался домой с дачи и решил написать развернутый коментарий.
Совсем не понятна мотивация за использование Rust. Компиляторов C/C++ для RV32 как грязи - бери любой (лучше всего от SiFive, у них есть готовый под винду). Но если захотелось чего-то эдакого, то почему бы не написать это на ассемблере ? Ваш проект очень простой, на асм хорошо ложится и это было бы полезно для изучающих архитектуру. Ну да ладно.
Почему модуль note не формирует сигнал ready ? Как драйвер этого усторйства узнает о том, что новая нота успешно детектирована ? Аналогичный вопрос по модулю микрофона. В basic_graphics_music у модуля микрофона нет сигнала ready и это большая беда - это не позволяет верхнему уровню узнать о том, что регистр данных содержит обновленные данные и весь код детектирования ноты очень сомнителен.
Загрузка программы через последовательный порт это круто! Но питоновый скрипт bin2hex здесь лишний, так как утилита objcopy умеет генерировать на выходе IntelHEX и еще много других полезных форматов. Ниже пример из моего проекта:
objcopy -v -O ihex -I binary --set-start 0x80000000 --change-addresses 0x80000000 hello.bin hello.hex
Не понятно зачем была вся эта возня с перекомпиляцией компилятора и атомарными инструкциями на одном ядре в однозадачном приложении ? Зачем Вам здесь понадобились мьютексы ? Если от мьютексов избавиться нельзя, то не проще было бы перегрузить методы lock/unlock или Rust этого не позволяет ?
Аналогичный вопрос: зачем в аллокаторе мьютексы на одном ядре и одном потоке ?
Не понял как сделано выключение нот. Включение вижу, не вижу где отправляется выключение и по какому событию.
Для себя сделал вывод: Rust это язык не для низкоуровнего программирования, так как необходимость в мьютексах (атомарных операциях) очень глубоко сидит в синтаксисе языка. В этом свете мне кажется очень странным решение Торвальдса затягивать Rust в ядро Linux - кто-то должен был его остановить от необдуманных поступков. :-)
PS: В ПЛИСах Gowin GW1NR (платы TangNano) есть большой кусок динамической SDRAM памяти - можно весь видео фрейм-буфер туда вынести.
PPS: А ссылка на репозиторий где ? :-)
DmitryZlobec Автор
07.01.2024 22:20+4-
В том то и дело что как грязи. Но и SiFve и Syntacore делают компиляторы для своих процессоров и их релизная политика зависит от реализации их собственных продуктов.
Простой проект для изучения архитектуры уже был - https://habr.com/ru/articles/726250/
Это Proof of concept. В перспективе делать на asm работу с MIDI сообщениями? Есть готовые крейты, но у них в зависимостях мьютексы. Достоинство и недостатки пакетной системы с зависимостями в одном флаконе. Тут все просто, особенность инструмента в том что ноты идут подряд, нота меняется старую выключаем, новую включаем. Скорости хватает. Что касается самого фильтра, то на канале подготовки лаб про это много обсуждений было, чем кончилось не помню. Опять же это POC. Возможно возьмусь за эту лабу и сделаю ее лучше.
Загрузка через последовательный порт пришла из проекта MIPSfpga и перенесена в проект YRV-Plus Юрием Панчулом. -O ihex дает не совсем тот результат, необходимый для загрузки, ток что все равно какой-то скрипт нужен. Ну и так как YRV бывает с шиной в 16 и в 32 бита то проще две прошивки делать.
Никакой перекомпиляции компилятора не было, я добавил две инструкции в ядро процессора YRV и получил возможность использовать "стоковый" Rust и порешал проблемы со всеми крейтами в которых была зависимость от mutex. Как минимум мне нужны будут крейты для работы с MIDI сообщениями. Достаточно глупо использовать Rust не используя всю мощь Cargo.
Это стандартный крейт https://crates.io/crates/linked_list_allocator Никакого колхоза. Для esp есть крейт который использует critical section https://github.com/esp-rs/esp-alloc
checkpoint
07.01.2024 22:20Спасибо за код, буду посмотреть.
Не понял вот это:
Загрузка через последовательный порт пришла из проекта MIPSfpga и перенесена в проект YRV-Plus Юрием Панчулом. -O ihex дает не совсем тот результат, необходимый для загрузки,
Я помню эту дискуссию, но я полагал что там стандартный IntelHEX, разве нет ? Может быть тогда имеет смысл исправить код самого YRV, чтобы он поддерживал стандартный формат ?
Аналогичным образом выполнен один из режимов загрузки отечественного микропроцессора СКИФ. Но там IntelHEX, я его туда cut-n-paste-ом с консоли запихиваю и все дела. :-)
DmitryZlobec Автор
07.01.2024 22:20Это два разных стандарта: IntelHEX и "Verilog IEEE Std. 1364-2005, §17.2.9 Loading memory data from a file" и оба хексы. Но у СКИФа судя по полноценной консольке все-таки софтовый парсер. Можно конечно поставить еще один YRV для консоли и загрузки, но как бутить тогда его )))
checkpoint
07.01.2024 22:20Если я правильно понимаю, то Verilog HEX это формат файла для инициализации массивов в момент синтеза, их можно инклудить прямо из .sv кода и это совсем не подходящий метод в данном случае. У Вас же исполняемый файл, который Вы генерируете стандартным компилятором и на это в компиляторе уже есть поддержка традиционного Intel HEX. Считаю, что YRV надо доработать на поддержку именно Intel HEX! Любопытно, на сколько это сложная задача.
Да, в СКИФе есть свой BROM в котором есть парсер Intel HEX и небольшой "монитор" для чтения/изменения ячеек памяти и передачи управления по адресу - как в старые добрые времена. :-)
Кстати, а можно ли YRV снабдить "монитором" на стадии синтеза ?
DmitryZlobec Автор
07.01.2024 22:20Парсер intelhex на vhdl видал, так что задача решаема. Я думаю что при некоторых компромиссах доработка будет несложной, надо более подробно ключики посмотреть.
Да это хороший кейс, поставить второй YRV и использовать его как монитор сам то проц невелик 2000 лутов, по нынешним временам ерунда))
checkpoint
07.01.2024 22:20Идея с монитором такая: при синтезе помещать код монитора в верхние адреса RAM, как на старых 8-битных ПК, и передавать ему управление при сбросе. В мониторе сделать парсер HEX и прочие мелкие прелести для отладки и вывода текста в консоль. Программа пользователя помещается в нижнюю область и может, при необходимости, смело затереть монитор и использовать эту область памяти, скажем, под стек или для данных.
Да, все хотел спросить, Вы пробовали синтезировать два ядра YRV с разделяемой памятью ?
DmitryZlobec Автор
07.01.2024 22:20Увы, я не знаю как на AHB-Lite два ядра правильно посадить. Во всех книжках одно ядро с селектором (((
-
Anzorik_228
Возможно от усталости в данный момент из-за работы эмоции берут выше, но автор молодец. Классная работа. Доступно и интересно! Респект!