Комментариев от @COKPOWEHEU по этой лекции нет, зато есть замечание от Вячеслава Разыкова

Георгий Владимирович, здравствуйте. Пересмотрел прошлогоднюю лекцию по конвейеру в RISC-V. Вы там затронули тему внеочередного выполнения инструкций. Понимаю, тема очень обширная, чтобы её охватить полностью, но предлагаю немного углубиться в этом году.

В частности, рассказать, что эта "магия" удаления "пузырьков" из пайплайна может потребовать внимания со стороны программиста, потому что эта оптимизация создаёт практические проблемы, которые требуют добавления барьеров синхронизации. Упомянуть, что в RISC-V это решается инструкциями fence, fence.i (у Паттерсона ещё sfence.vma упоминается, хотя их много разных).

Также можно продемонстрировать такой хрестоматийный пример, который моделирует ситуацию, когда нужна fence. Здесь программа запускается на двух hart’ах. Один пишет в буфер и выставляет флаг завершения записи. Другой ожидает выставления флага, а потом читает из буфера.

   1 .equ    BUFFER_SIZE, 32
   2 
   3 .data
   4 flag:   .byte 0
   5 buffer: .zero 32
   6 
   7 
   8 .section .text._start
   9 .global _start
  10 _start:
  11     csrr    t0,mhartid
  12 
  13     beqz    t0,hart0_entry
  14     bnez    t0,hart1_entry
  15     
  16     _main_wait:
  17         j   _hart0_wait
  18 
  19 
  20 hart0_entry:
  21     # Записываем данные в буфер
  22     la  t0,buffer
  23     li  t1,BUFFER_SIZE
  24     _buffer_write:
  25         sb      t1,0(t0)
  26         addi    t0,t0,1
  27         addi    t1,t1,-1
  28         bnez    t1,_buffer_write
  29 
  30     # fence   rw,rw
  31 
  32     # Устанавливаем флаг окончания записи в буфер
  33     la  t2,flag
  34     li  t3,1
  35     sb  t3,0(t2)
  36 
  37     _hart0_wait:
  38         j   _hart0_wait
  39 
  40 
  41 hart1_entry:
  42     # Ждём флага окончания записи
  43     la  t0,flag
  44     _flag_wait:
  45         lb      t1,0(t0)
  46         beqz    t1,_flag_wait
  47 
  48     # fence   rw,rw
  49 
  50     # Читаем из буфера
  51     la  t2,buffer
  52     li  t3,BUFFER_SIZE
  53     _buffer_read:
  54         lb      t4,0(t2)
  55         addi    t2,t2,1
  56         addi    t3,t3,-1
  57         bnez    t3,_buffer_read
  58 
  59     _hart1_wait:
  60         j   _hart1_wait

В этом коде блоки инструкции на строках 22-28 и строках 33-35 не зависят ни по данным, ни по регистрам, аналогично со строками 43-46 и 51-57, поэтому процессор или подсистема памяти может переупорядочить операции так, что другой hart увидит обновление flag раньше, чем завершится запись в buffer. В итоге программа работает не так, как ожидается. Но если раскомментировать строки с fence rw,rw, то все операции чтения/записи в 22-28 будут выполнены до 33-35, а 43-46 до 51-57 и поведение программы будет соответствовать ожидаемому.

Я его сейчас запустил на spike — проверить алгоритм, однако воспроизвести такой баг даже на реальном железе нелегко, а эмуляторов, способных на такое, вероятно, не существует. Но для общего представления можно разобрать пример теоретически. Вещь-то очень важная в больших чипах на самом деле. В операционках встречается повсеместно. В linux через макрос mb() задействуется. Да даже если с riscv-tests человек начнет работать, сразу с ней столкнется.

LecturesCMC/ArchitectureAssembler2026/12_Pipeline/COKPOWEHEU (последним исправлял пользователь FrBrGeorge 2026-05-02 19:41:08)