Как стать автором
Обновить

RAD для софт-процессоров и немного «сферических коней в вакууме»

Время на прочтение7 мин
Количество просмотров2.5K

 Разработка или выбор управляющего контроллера для встраиваемой системы на ПЛИС –актуальная и не всегда тривиальная задача. Часто выбор падает в пользу широкораспространенных IP-ядер, обладающих развитой программно-аппаратной структурой – поддержка высокопроизводительных шин, периферийный устройств, прикладное программное обеспечение и, в ряде случаев, операционных систем (в основном Linux, Free-RTOS).  Одними из причин данного выбора являются желание обеспечить достаточную производительность и иметь под рукой готовый инструментарий для разработки программного обеспечения.

В том случае, если применяемая в проекте ПЛИС не содержит аппаратных процессорных ядер, реализация полноценного процессорного ядра может быть избыточной, или вести к усложнению программного его обеспечения, а следовательно приведет к увеличению затрат на его разработку. Кроме того, универсальное софт-ядро будет, так или иначе, занимать дефицитные ресурсы программируемой логики. Специализированный софт-процессор будет более оптимальным решением в свете экономии ресурсов логики – за счет адаптированной системы команд, небольшого количества регистров, разрядности данных (вплоть до некратной 8битам). Согласование с периферийными устройствами – проблема в основном согласования шин и протоколов. Заменой сложной системы обработки прерываний может служить многопоточная архитектура процессора.

Стековые софт-процессоры и контекст потока

Обычно многопоточные процессоры имеют одно АЛУ и несколько наборов регистров (иногда называемых «теневыми» регистрами) для хранения контекста потока, следовательно, чем больше требуется потоков, тем будут больше накладные расходы логики и памяти. Среди разнообразия архитектур софт-процессорных ядер следует выделить стековую архитектуру. Такие процессоры часто называют еще Форт-процессорами, так как чаще всего их ассемблер естественным образом поддерживает подмножество команд языка Форт.

У стековых процессоров есть интересное свойство –это небольшой размер контекста потока. Поскольку роль регистров выполняет стек, при переключении на другой поток необязательно иметь полный комплект регистров общего назначения - достаточно переключить указатель стека [1]. В простейшем случае контекст потока можно ограничить небольшим набором указателей - стека, стека возвратов и счетчик команд потока. При наличии нескольких  определяемых спецификой задач потоков вычислений компактность многопотоковой схемы будет важнее пиковой производительности, а задача реализации процессорной системы в ПЛИС минимального объема является актуальной в свете, например, нового семейства Spartan-7 число ячеек в устройствах которого невелико.

Идеи и методика реализации многопоточного софт-процессора изложены в работе [1]. Развитие работ в этом направлении привело к появлению свободного компилятора языка Python для процессоров стековой архитектуры [2]. Это решает проблему разработки системного программного обеспечения для софт-процессора. Более того, данный язык благодаря простому синтаксису и поддержке различных парадигм программирования является популярным и удобен для начинающих программистов.

Инструментальные средства для упрощенного проектирования IP-ядер

Известен также инструментарий MyHDL [3], позволяющий описывать аппаратные модули и узлы на языке Python. Синтаксис описания проще VHDL, сам инструментарий менее требователен к ресурсам системы, чем фирменные среды разработки. Помимо самого описания MyHDL позволяет описывать тестовые последовательности и логически симулировать работу модулей [4].

Потенциально, совместное применение инструментариев MyHDL  и Uzh позволит вести разработку софт-процессора и компилятора языка высокого уровня для него на одном языке, что позволит снизить уровень сложности задачи. В частности это будет полезно в проектах, связанных с начальным обучением программированию, разработки цифровой техники, систем управления. На данный момент предлагаемые подходы к начальному обучению работе с программируемой логикой находятся на стадии поиска эффективных решений и не всегда методически выдержаны [5-7]. Единый стиль описания может помочь сгладить сложные момент в процессе освоения ПЛИС, к тому же некоторые популярные конструкторы и наборы с программируемыми блоками используют Python для задания рабочей программы.

Комбинационные логические схемы и последовательные автоматы описываются и симулируются достаточно не сложно [8,9], откуда можно сделать вывод, что, придерживаясь определенной концепции описать прототип многопоточного софт-процессора на поведенческом уровне достаточно просто.

Реализация простого многопоточного процессора на MyHDL

Учитывая идеи и базовую архитектуру процессора, предложенные в [1], разрабатываемый процессор будет иметь следующие особенности: Гарвардская архитектура с раздельными памятями программ и данных, стеки физически отображаются на память данных, для хранения контекста потока предусмотрены наборы теневых регистров.

Однотактовые и конвейерные пути решения для начальной реализации не очевидны, и с учетом того, что для софт-процессора не требуется сверхвысокой производительности, командный цикл процессора можно сделать многотактным. В первом приближении достаточно четырех стадий выполнения: чтение контекста потока, выборка операндов в кеширующие регистры, выполнение команды, переключение на следующий поток.

Разрядность памяти программ, данных, размеры стеков и количество потоков задается параметрически при помощи ряда переменных:

bits=16 
Nthread=8 
RAMsize=256 
ROMsize=2048 
STACKsize=8 
RS_BASE=64
DS_BASE=8

Вход процессора - тактовый сигнал и сигнал сброса, плюс шины данных и адреса для внешних устройств (дополнительные сигналы – опционально). В ядро процессора будут входить переключатель состояний процессора, счетчик текущего потока, наборы указателей стеков и счетчика команд для каждого из потоков, регистры текущего контекста, память данных и программ.

Сама микроархитектура ядра реализуется через ряд функций, отвечающих за генерацию последовательных схем.  Счетчик состояний процессора реализуется просто – установка в ноль при сигнале сброса, иначе – инкремент на каждый такт.

Логика работы узлов процессора на каждом из этапов выполнения описывается в отдельной функции:

def gor(reset, clk, dat, prt):
	state=Signal(modbv(0, min=0, max=4))
	thread=Signal(modbv(0, min=0, max=Nthread))
	th_sp = [Signal(intbv(0)[bits:]) for i in range(Nthread)]
	th_rp = [Signal(intbv(0)[bits:]) for i in range(Nthread)]
	th_ip = [Signal(intbv(0)[bits:]) for i in range(Nthread)]
	sp, rp, ipreg, rt = [Signal(intbv(0)[bits:]) for i in range(4)]
	cmd = Signal(intbv(0)[9:])
	tos, sos, tdata = [Signal(intbv(0)[bits:]) for i in range(3)]
	D_RAM = [Signal(intbv(0)[bits:]) for i in range(RAMsize)]
	C_ROM = [Signal(intbv(0)[9:]) for i in range(ROMsize)] 

Первый этап - переключение контекста - считываются нужные рабочие регистры из наборов для текущего потока:

@always_comb def st_sw():
  if reset==1:
    if state==task_sw:
      sp.next=th_sp[thread]
      rp.next=th_rp[thread]
      ipreg.next=th_ip[thread] 

Чтение операндов – считываются из памяти верхние элементы стеков, текущая команда потока и текущий внешний вход:

@always(clk.posedge) def st_get():
  if reset==1:
    if state==get_data:
      tos.next=D_RAM[sp]
      sos.next=D_RAM[sp+1]
      rt.next=D_RAM[rp]
      cmd.next=C_ROM[ipreg]
      tdata.next=dat 

Третий этап - выполнение команд.

@always(clk.posedge) def st_ex():
  if reset==1:
    if state==execute:
      # unary                          
      if cmd == nop:
        D_RAM[sp].next= tos
        th_ip[thread].next=ipreg+1
      elif cmd == noti:
        D_RAM[sp].next= ~tos
        th_ip[thread].next=ipreg+1
      #alu                         
      elif cmd == add:
        D_RAM[sp+1].next=tos+sos
        th_sp[thread].next=sp+1
        th_ip[thread].next=ipreg+1
      elif cmd == andi:
        D_RAM[sp+1].next=tos&sos
        th_sp[thread].next=sp+1
        th_ip[thread].next=ipreg+1 и т.д.

Состав команд может быть оптимизирован для каждой конкретной задачи, единственное требование – желательна поддержка команд абстрактной стековой машины [1] для построения более эффективного кода при компиляции с высокоуровнего языка. Некоторые идеи по реализации работы с константами взяты из работы [10].

Финальный этап - инкремент счетчика потоков:

@always(clk.posedge)
def st_sv(): # task_sw = 3
if reset==1:
# get_data = 1
if state== save_task:
thread.next=thread+1
else:
thread.next = 0

Все описанные блоки возвращаются при завершении описании основной функции процессорного ядра:

return st_swt, st_sw, st_get, st_ex, st_sv

Полученное ядро можно логически протестировать - определяется тестовая функция, генеруется тактовая последовательность, подается сбросовый сигнал:
def test(): reset = Signal(bool(0)) clk = Signal(bool(0)) #C_ROM = (noti, dup, drop, nop, nop, nop, nop, nop, nop) #C_ROM = (lit0, 5|0x100, lit0, 2|0x100, add, nop, nop, nop, nop, nop, nop) dat, prt = [Signal(intbv(0)[bits:]) for i in range(2)] test = gor(reset, clk, dat, prt)

@always(delay(10))
def gen():
    clk.next = not clk

@always(delay(50))
def go():
    reset.next = 1

return test, gen, go

def simulate(timesteps):
    tb = traceSignals(test)
    sim = Simulation(tb)
    sim.run(timesteps)

Результат симуляции автоматически выгружается в файл .vcd (в данном случае - test.vcd), который потом можно будет открыть в любом просмотрщике временных диаграмм.

На рисунке представлены временные диаграммы работы сгенерированного восьмипоточного 16-битного процессора. Все потоки начинают работу с одного адреса, но их стеки данных и возвратов находятся по разным адресам. Можно отследить изменения счетчиков команд потоков, загрузку констант, манипуляции с данными на стеках.

Рис. Результат симуляции работы софт-процессора.

При необходимости MyHDL код может быть транслирован в код налюбом из HDL языков – в Verilog или в VHDL. Полученные таким образом файлы в дальнейшем могут быть использованы в проектах сред разработки под выбранное семейство ПЛИС:

def convert():
  reset = Signal(bool(0))
  clk = Signal(bool(0))
  dat, prt = [Signal(intbv(0)[bits:]) for i in range(2)]
  toVHDL(gor, reset, clk, dat, prt)

Заключение

Был продемонстрирован простой маршрут быстрого прототипирования софт-процессора, позволяющий при незначительных затратах времени и вычислительных ресурсов проверить концептуальную идею на работоспособность. Полученные результаты – транслированные HDL-файлы могут служить основой для дальнейшего развития и оптимизации проекта уже с учетом особенностей текущей серии ПЛИС. В частности, для представленного примера финальный VHDL код будет генерировать память на дефицитных для ПЛИС регистрах, а более предпочтительным является задействование ресурсов встроенных блоков памяти.

Библиографический список

  1. Советов П.Н., Тарасов И.Е. Разработка многопоточного софт-процессора со стековой архитектурой на основе совместной оптимизации программной модели и системной архитектуры. Многоядерные процессоры, параллельное программирование, плис, системы обработки сигналов, вып.7. 2017, стр.8-19.

  2. GitHub - true-grue_uzh_ Uzh compiler.

  3. MyHDL

  4. Начинаем FPGA на Python (статья на Habr).

  5. Юрий Панчул. Следущие шаги в черной магии процессоростроения после того, как вы освоили Харрис & Харрис.

  6. Жельнио Станислав. Школа по основам цифровой схемотехники_ Новосибирск — Ок, Красноярск — приготовиться (статья на Habr).

  7. Жельнио Станислав. Процессорное ядро SchoolMIPS и его использование для обучения основам микроархитектуры процессора.

  8. MyHDL examples.

  9. Felton Christopher. Developing FPGA-DSP IP with Python.

  10. Forth-процессор на VHDL (статья на Habr).

Теги:
Хабы:
Всего голосов 13: ↑12 и ↓1+15
Комментарии0

Публикации

Истории

Работа

Data Scientist
78 вакансий
Python разработчик
121 вакансия

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань