Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

Предыдущая версия справа и слева Предыдущая версия
Следующая версия
Предыдущая версия
sim [2015/09/03 23:17]
org
sim [2019/12/03 11:21] (текущий)
org
Строка 1: Строка 1:
-====== ​Подходы к симуляции ​======+Подходы к симуляции
  
 Для упрощения представления примем,​ что реальные логические (цифровые) схемы работают как потоки жидкости. Для упрощения представления примем,​ что реальные логические (цифровые) схемы работают как потоки жидкости.
Строка 15: Строка 15:
 Если на затворе транзистора x (то есть "не пойми чего"​),​ то то же самое будет и на выходе. А если на затворе "​z"​ (то есть затвор "​оторван"​ от схемы),​ то на выходе тоже будет "​z"​ (через такой транзистор ток не проходит). Если на затворе транзистора x (то есть "не пойми чего"​),​ то то же самое будет и на выходе. А если на затворе "​z"​ (то есть затвор "​оторван"​ от схемы),​ то на выходе тоже будет "​z"​ (через такой транзистор ток не проходит).
  
-====== ​Статическая симуляция ​======+## Статическая симуляция
  
 По условию задачи мы имеем процессор,​ а также 2 вспомогательных устройства - APU и PPU. При этом тактовый сигнал для этих устройств общий - CLK. По условию задачи мы имеем процессор,​ а также 2 вспомогательных устройства - APU и PPU. При этом тактовый сигнал для этих устройств общий - CLK.
  
-Если мы напишем на Си отдельно симуляцию всей логики 6502, отдельно для APU и PPU, то возникает вопрос : что выполнять в первую очередь ? Хотя 6502 является black-box, однако он соединен с внешним миром шинами.+Если мы напишем на Си отдельно симуляцию всей логики 6502, отдельно для APU и PPU, то возникает вопрос:​ что выполнять в первую очередь?​ Хотя 6502 является black-box, однако он соединен с внешним миром шинами.
  
-Простой пример : запись в регистр PPU. Звучит просто,​ но рассмотрим что происходит при этом :+Простой пример:​ запись в регистр PPU. Звучит просто,​ но рассмотрим что происходит при этом:
   * процессор (а точнее ядро процессора 6502 встроенного в APU) начинает выполнение инструкции Store. Инструкция декодируется и в итоге значение регистра подается на шину данных APU, которая соединена с шиной данных PPU. При этом на адресную шину выставляется адрес регистра PPU.   * процессор (а точнее ядро процессора 6502 встроенного в APU) начинает выполнение инструкции Store. Инструкция декодируется и в итоге значение регистра подается на шину данных APU, которая соединена с шиной данных PPU. При этом на адресную шину выставляется адрес регистра PPU.
   * Дополнительная логика на материнской плате NES определяет что выставленный адрес соответствует регистрам PPU ($2000 например). Логика включает PPU CS (chip select) (а точнее делает контакт #DBE = 0), также она подает сигнал R/W (WR=1) и выставляет контакты PPU RS0-RS2 (выбор регистра)   * Дополнительная логика на материнской плате NES определяет что выставленный адрес соответствует регистрам PPU ($2000 например). Логика включает PPU CS (chip select) (а точнее делает контакт #DBE = 0), также она подает сигнал R/W (WR=1) и выставляет контакты PPU RS0-RS2 (выбор регистра)
   * В это время логика Register Select внутри PPU запускает процесс обновления затребованного регистра. При этом все внутренние схемы PPU мгновенно реагируют на изменение регистра и соответственно изменяется логика работы всего PPU.   * В это время логика Register Select внутри PPU запускает процесс обновления затребованного регистра. При этом все внутренние схемы PPU мгновенно реагируют на изменение регистра и соответственно изменяется логика работы всего PPU.
  
-Какие могут возникнуть тут проблемы ? Главная проблема - это конечно же propagation delay. Или что было раньше - курица или яйцо. А ведь ситуации могут быть и похлеще,​ например спрайтовая DMA : когда внутренняя схема APU отключает ядро от шины и подвешивает процессор,​ при этом в PPU пересылается поток байт, для обновления спрайтовой памяти.+Какие могут возникнуть тут проблемы?​ Главная проблема - это конечно же propagation delay. Или что было раньше - курица или яйцо. А ведь ситуации могут быть и похлеще,​ например спрайтовая DMA: когда внутренняя схема APU отключает ядро от шины и подвешивает процессор,​ при этом в PPU пересылается поток байт, для обновления спрайтовой памяти.
  
-В общем случае проблема выглядит так (упрощенно) :+В общем случае проблема выглядит так (упрощенно):​
   * Схема A имеет выходы aaa и входы bbb.   * Схема A имеет выходы aaa и входы bbb.
   * Схема B имеет выходы bbb и входы aaa. (то есть схемы выдают друг на друга управляющие сигналы)   * Схема B имеет выходы bbb и входы aaa. (то есть схемы выдают друг на друга управляющие сигналы)
Строка 37: Строка 37:
 То есть мы имеем более общий вариант,​ частным вариантом которого являются "​транзисторные гонки"​ (конфликты шин). Только теперь в качестве устройств у нас не отдельные компоненты схемы, а сразу микросхемы целиком (в качестве конфликтующих девайсов). То есть мы имеем более общий вариант,​ частным вариантом которого являются "​транзисторные гонки"​ (конфликты шин). Только теперь в качестве устройств у нас не отдельные компоненты схемы, а сразу микросхемы целиком (в качестве конфликтующих девайсов).
  
-Выход из этой ситуации заключается в следующем : девайсы симулируются по-очереди,​ в зависимости от их приоритета. Например при записи в регистр PPU вначале выполняется ядро 6502, а потом весь PPU.+Выход из этой ситуации заключается в следующем:​ девайсы симулируются по-очереди,​ в зависимости от их приоритета. Например при записи в регистр PPU вначале выполняется ядро 6502, а потом весь PPU.
 Вообще в APU главным является не ядро, а управляющая логика APU, которая решает что главнее в данный момент - внутренние устройства APU (звуковые каналы) или ядро 6502. Вообще в APU главным является не ядро, а управляющая логика APU, которая решает что главнее в данный момент - внутренние устройства APU (звуковые каналы) или ядро 6502.
-Поэтому статический подход к симуляции полутакта CLK заключается в следующем :+Поэтому статический подход к симуляции полутакта CLK заключается в следующем:​
   * симулировать все внутренние схемы APU (делитель частоты,​ frame counter, выбор регистра и выдача его на шину данных,​ звуковые схемы)   * симулировать все внутренние схемы APU (делитель частоты,​ frame counter, выбор регистра и выдача его на шину данных,​ звуковые схемы)
   * симулировать ядро 6502 (выполнить инструкцию,​ получить или выдать значение на шину данных)   * симулировать ядро 6502 (выполнить инструкцию,​ получить или выдать значение на шину данных)
   * симулировать PPU   * симулировать PPU
  
-Но опять же остается проблема : что делать если мы уже симулировали всю схему (например 6502), но последующая симуляцию другой схемы поменяла входные данные. Например ​чтение регистра PPU :+Но опять же остается проблема:​ что делать если мы уже симулировали всю схему (например 6502), но последующая симуляцию другой схемы поменяла входные данные. Напримерчтение регистра PPU:
   * мы уже выполнили инструкцию Load (при этом получили с шины данных какую-то фигню)   * мы уже выполнили инструкцию Load (при этом получили с шины данных какую-то фигню)
   * настала очередь симуляции PPU, мы выдаем на шину данных верное значение регистра,​ но ведь симуляция 6502 уже прошла!   * настала очередь симуляции PPU, мы выдаем на шину данных верное значение регистра,​ но ведь симуляция 6502 уже прошла!
   * жопа   * жопа
  
-====== ​Реактивное программирование ​======+## Реактивное программирование
  
 Все вы запомнили чувака http://​www.youtube.com/​watch?​v=SyWFvn0I6m8 который выкупал про реактивное программирование :-) Все вы запомнили чувака http://​www.youtube.com/​watch?​v=SyWFvn0I6m8 который выкупал про реактивное программирование :-)
  
-Я залез в вики и почитал немного про парадигмы программирования. Оказывается всё что я до этого делал (статический подход) - называется императивным программированием :+Я залез в вики и почитал немного про парадигмы программирования. Оказывается всё что я до этого делал (статический подход) - называется императивным программированием:​
  
 <code "​c">​ <code "​c">​
Строка 79: Строка 79:
 Таким образом проблема "​что выполнить раньше"​ снимается,​ причём глобально,​ вплоть до уровня симуляции отдельных транзисторов. Таким образом проблема "​что выполнить раньше"​ снимается,​ причём глобально,​ вплоть до уровня симуляции отдельных транзисторов.
  
-====== ​Стабилизация схемы ​======+## Стабилизация схемы
  
 С помощью реактивного подхода можно поделить схему на компоненты,​ при этом обозначив их входы, выходы и двунаправленные соединения (шины). С помощью реактивного подхода можно поделить схему на компоненты,​ при этом обозначив их входы, выходы и двунаправленные соединения (шины).
Строка 85: Строка 85:
 После изменения тактового сигнала - схема запускается,​ в зависимости от входных сигналов изменяет своё внутреннее состояние и выдаёт наружу выходы. Далее эти выходы используются как входы для другой схемы по цепочке. После изменения тактового сигнала - схема запускается,​ в зависимости от входных сигналов изменяет своё внутреннее состояние и выдаёт наружу выходы. Далее эти выходы используются как входы для другой схемы по цепочке.
  
-А теперь рассмотрим пример когда исходная система передает своё состояние на выход другой,​ а выход другой системы подается на вход первой. При этом обе системы формируют выходы на основе своих внутренних состояний. В качестве примера будет выступать обыкновенный регистр :+А теперь рассмотрим пример когда исходная система передает своё состояние на выход другой,​ а выход другой системы подается на вход первой. При этом обе системы формируют выходы на основе своих внутренних состояний. В качестве примера будет выступать обыкновенный регистр:​
  
 {{::​reg.jpg}} {{::​reg.jpg}}
  
-Логика работы следующая :+Логика работы следующая:​
   * Во время CLK = 0 вход регистра отсоединен и входная защелка рефрешится старым значением   * Во время CLK = 0 вход регистра отсоединен и входная защелка рефрешится старым значением
   * Во время CLK = 1 выход отрезается (транзистором /CLK), чтобы не конфликтовать,​ а новое значение подается на вход **in**, но только в том случае,​ если открыт транзистор ENABLE (разрешить запись)   * Во время CLK = 1 выход отрезается (транзистором /CLK), чтобы не конфликтовать,​ а новое значение подается на вход **in**, но только в том случае,​ если открыт транзистор ENABLE (разрешить запись)
Строка 95: Строка 95:
   * Если CLK = 1, а ENABLE = 0, то на выход подается остаточный заряд с затвора защёлки (то есть старое значение)   * Если CLK = 1, а ENABLE = 0, то на выход подается остаточный заряд с затвора защёлки (то есть старое значение)
  
-Псевдокод в этом случае будет такой :+Псевдокод в этом случае будет такой:
 <code "​txt">​ <code "​txt">​
  
Строка 119: Строка 119:
 </​code>​ </​code>​
  
-В этом случае все входы и выходы всех компонентов схемы являются реактивными связями (почему это не пишут в обучалках по Verilog - я хз, ведь именно с этого надо вести речь). Как будет синтезироваться netlist схемы нам не интересно (это вообще отдельная тема), но вот как теперь будет проходить симуляция : вполне понятно!+В этом случае все входы и выходы всех компонентов схемы являются реактивными связями (почему это не пишут в обучалках по Verilog - я хз, ведь именно с этого надо вести речь). Как будет синтезироваться netlist схемы нам не интересно (это вообще отдельная тема), но вот как теперь будет проходить симуляция:​ вполне понятно!
  
-При симуляции происходит следующее :+При симуляции происходит следующее:​
   * Выбираются все блоки **at**. Эти блоки являются реактивными по отношению к какому-либо сигналу. В данном случае реактивным сигналом будет являться сигнал CLK. Ядро симулятора постоянно следит за CLK, и как только он изменяет своё значение (с 0 на 1, или наоборот) - запускается соответствующий блок **at**. Блоки которые не имеют реактивной связи нас не интересуют (они как будто "​заморожены"​).   * Выбираются все блоки **at**. Эти блоки являются реактивными по отношению к какому-либо сигналу. В данном случае реактивным сигналом будет являться сигнал CLK. Ядро симулятора постоянно следит за CLK, и как только он изменяет своё значение (с 0 на 1, или наоборот) - запускается соответствующий блок **at**. Блоки которые не имеют реактивной связи нас не интересуют (они как будто "​заморожены"​).
   * Последовательность симуляции выбранных at-блоков не имеет значения,​ поскольку все входы и выходы также реактивно связаны   * Последовательность симуляции выбранных at-блоков не имеет значения,​ поскольку все входы и выходы также реактивно связаны
Строка 127: Строка 127:
   * Порядок расположения схем выбран не случайно (вначале reg, потом some_circuit),​ чтобы показать особенность реактивного "​выполнения"​ блоков. ​   * Порядок расположения схем выбран не случайно (вначале reg, потом some_circuit),​ чтобы показать особенность реактивного "​выполнения"​ блоков. ​
  
-Начинаем исполнение :+Начинаем исполнение:​
   * Первым на очереди стоит блок reg, его входы regnew и enable пока не определены (то есть равны "​x"​)   * Первым на очереди стоит блок reg, его входы regnew и enable пока не определены (то есть равны "​x"​)
   * В этом случае выход блока #out будет просто инверсией текущего значения регистра not(regvalue).   * В этом случае выход блока #out будет просто инверсией текущего значения регистра not(regvalue).
Строка 135: Строка 135:
   * Схема стабилизировалась,​ значит все блоки выполнились и мы ждём изменения сигнала CLK, чтобы начать всё заново,​ но логика работы блоков уже поменяется (так как будут загружены блоки **at** для CLK = 1).   * Схема стабилизировалась,​ значит все блоки выполнились и мы ждём изменения сигнала CLK, чтобы начать всё заново,​ но логика работы блоков уже поменяется (так как будут загружены блоки **at** для CLK = 1).
  
-А что будет если схема не сможет стабилизироваться ? Что если regnew или enable будет всегда меняться и нам придётся выполнять блок **reg** снова и снова? Да ничего особенного :-) Такая ситуация называется "race condition"​ и обычно не возникает. В частности в процессоре 6502 для стабилизации всех схем достаточно не более 10 итераций. Для того, чтобы наша схема не начала бесконечный цикл мы просто ставим таймаут на количество итераций и если их количество стало ну скажем - больше 100, то просто выводим ошибку "Race condition!"​ и до свидания :-)+А что будет если схема не сможет стабилизироваться?​ Что если regnew или enable будет всегда меняться и нам придётся выполнять блок **reg** снова и снова? Да ничего особенного :-) Такая ситуация называется "race condition"​ и обычно не возникает. В частности в процессоре 6502 для стабилизации всех схем достаточно не более 10 итераций. Для того, чтобы наша схема не начала бесконечный цикл мы просто ставим таймаут на количество итераций и если их количество стало ну скажем - больше 100, то просто выводим ошибку "Race condition!"​ и до свидания :-)
  
 Ну короче вы поняли к чему я клоню :-) Для симуляции всех микропроцессоров и выполнения нашей задачи проще будет не изобретать велосипед,​ а взять и написать реализацию на Verilog. Только не простую,​ а интерактивную. Ну короче вы поняли к чему я клоню :-) Для симуляции всех микропроцессоров и выполнения нашей задачи проще будет не изобретать велосипед,​ а взять и написать реализацию на Verilog. Только не простую,​ а интерактивную.
  
-====== ​Verilog ​======+## Verilog
  
 Из имеющихся открытых реализаций Verilog самый адекватный - это Icarus Verilog (http://​iverilog.icarus.com). Работает он как консольное приложение и содержит в своём составе компилятор Verilog и симулятор. ​ Из имеющихся открытых реализаций Verilog самый адекватный - это Icarus Verilog (http://​iverilog.icarus.com). Работает он как консольное приложение и содержит в своём составе компилятор Verilog и симулятор. ​
Строка 147: Строка 147:
 То есть нам нужно сделать так, чтобы Verilog стал скриптовым языком,​ а псевдо-устройства стали встроенными функциями по типу $display. При этом не каким-то тормозным интерпретатором нетлиста,​ а самым настоящим рекомпилятором нетлиста в X86-код, чтобы максимально эффективно "​выполнять"​ симулируемые блоки. То есть нам нужно сделать так, чтобы Verilog стал скриптовым языком,​ а псевдо-устройства стали встроенными функциями по типу $display. При этом не каким-то тормозным интерпретатором нетлиста,​ а самым настоящим рекомпилятором нетлиста в X86-код, чтобы максимально эффективно "​выполнять"​ симулируемые блоки.
  
-Такая реализация обеспечит нам серьезный задел на будущее :+Такая реализация обеспечит нам серьезный задел на будущее:​
   * Симулировать можно любые системы и процессоры,​ просто перегнав их в Verilog   * Симулировать можно любые системы и процессоры,​ просто перегнав их в Verilog
   * Система встраиваемых функций позволяет реализовать самые любые псевдо-устройства   * Система встраиваемых функций позволяет реализовать самые любые псевдо-устройства
Строка 153: Строка 153:
   * HardWareMan будет тоже очень рад, потому что он получит готовую реализацию PPU на Verilog :-)   * HardWareMan будет тоже очень рад, потому что он получит готовую реализацию PPU на Verilog :-)
  
-Задача ясна и понятна :+Задача ясна и понятна:​
   * Написать лексический анализатор Verlog-синтаксиса   * Написать лексический анализатор Verlog-синтаксиса
   * Написать парсер синтаксиса в синтаксическое дерево   * Написать парсер синтаксиса в синтаксическое дерево
Строка 162: Строка 162:
 http://​irs.nntu.ru/​globals/​files/​bukvarev/​verilog.pdf http://​irs.nntu.ru/​globals/​files/​bukvarev/​verilog.pdf
  
-Описание внутреннего устройства виртуальной машины - [[sim:​breaksvm|BreaksVM]] 
  • Показать страницу