VHDL: приемный модуль случайным образом дает сбой при подсчете битов

9

Фон

Это личный проект; это касается подключения FPGA к N64, байтовые значения, которые получает FPGA, затем отправляются через UART на мой компьютер. Это на самом деле работает довольно хорошо! К сожалению, в случайное время устройство выйдет из строя, а затем восстановится. Благодаря отладке мне удалось найти проблему, однако я нахожусь в тупике, как ее исправить, потому что я довольно некомпетентен с VHDL.

Я играю с VHDL уже пару дней и, возможно, я не в состоянии решить эту проблему.

Проблема

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

По сути, N64 отправляет 9 бит данных, включая бит STOP. Счетчик считает полученные биты данных, и когда я достигну 9 бит, FPGA начнет передачу через UART.

Вот правильное поведение: введите описание изображения здесь

ПЛИС - это синяя форма волны, а оранжевая форма - это вход N64. На время приема моя ПЛИС "эхом" передает сигнал ввода для целей отладки. После того, как FPGA сосчитает до 9, она начинает передавать данные через UART. Обратите внимание, что количество цифровых выводов равно 9, и выход FPGA становится НИЗКИМ сразу после окончания N64.

Вот пример сбоя:

введите описание изображения здесь

Обратите внимание, что счетчик пропускает биты 2 и 7! FPGA достигает конца, ожидая следующего бита старта от N64, но ничего. Таким образом, время FPGA истекает и восстанавливается.

Это VHDL для модуля приема N64. Он содержит счетчик: s_bitCount.

library IEEE;
use IEEE.STD_LOGIC_1164.all;   
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity N64RX is
     port(
         N64RXD : in STD_LOGIC;                    --Data input
         clk25 : in STD_LOGIC;
         clr : in STD_LOGIC; 
         tdre : in STD_LOGIC;                      --detects when UART is ready
         transmit : out STD_LOGIC;                 --Signal to UART to transmit  
         sel : out STD_LOGIC; 
         echoSig : out STD_LOGIC;
         bitcount : out STD_LOGIC_VECTOR(3 downto 0);
         data : out STD_LOGIC_VECTOR(3 downto 0)   --The significant nibble
         );
end N64RX;

--}} End of automatically maintained section

architecture N64RX of N64RX is 

type state_type is (start, delay2us, sigSample, waitForStop, waitForStart, timeout, count9bits, sendToUART);

signal state: state_type;
signal s_sel, s_echoSig, s_timeoutDetect : STD_LOGIC;
signal s_baudCount : STD_LOGIC_VECTOR(6 downto 0);  --Counting variable for baud rate in delay
signal s_bitCount : STD_LOGIC_VECTOR(3 downto 0);  --Counting variable for number of bits recieved 
signal s_data : STD_LOGIC_VECTOR(8 downto 0);   --Signal for data

constant delay : STD_LOGIC_VECTOR(6 downto 0) := "0110010";  --Provided 25MHz, 50 cycles is 2us 
constant delayLong : STD_LOGIC_VECTOR(6 downto 0) := "1100100";

begin 

n64RX: process(clk25, N64RXD, clr, tdre)
begin
    if clr = '1' then
        s_timeoutDetect <= '0';
        s_echoSig <= '1';
        s_sel <= '0';
        state <= start;
        s_data <= "000000000";
        transmit <= '0'; 
        s_bitCount <= "0000";
        s_baudCount <= "0000000";  
    elsif (clk25'event and clk25 = '1') then    --on rising edge of clock input
        case state is
            when start =>   
                --s_timeoutDetect <= '0';
                s_sel <= '0';
                transmit <= '0';        --Don't request UART to transfer   
                s_data <= "000000000";
                s_bitCount <= X"0";   
                if N64RXD = '1' then
                    state <= start;
                elsif N64RXD = '0' then     --if Start bit detected
                    state <= delay2us;
                end if;    

            when delay2us =>                 --wait two microseconds to sample
                --s_timeoutDetect <= '0';
                s_sel <= '1';
                s_echoSig <= '0';
                if s_baudCount >= delay then    
                    state <= sigSample;
                else
                    s_baudCount <= s_baudCount + 1;
                    state <= delay2us;
                end if;  

            when sigSample => 
                --s_timeoutDetect <= '1';
                s_echoSig <= N64RXD;
                s_bitCount <= s_bitCount + 1;
                s_baudcount <= "0000000";
                s_data <= s_data(7 downto 0) & N64RXD;      
                state <= waitForStop;   

            when waitForStop => 
                s_echoSig <= N64RXD;
                if N64RXD = '0' then
                    state <= waitForStop;
                elsif N64RXD = '1' then
                    state <= waitForStart;
                end if;   

            when waitForStart => 
                s_echoSig <= '1';
                s_baudCount <= s_baudCount + 1; 
                if N64RXD = '0' then 
                    s_baudCount <= "0000000";
                    state <= delay2us;
                elsif N64RXD = '1' then 
                    if s_baudCount >= delayLong then
                        state <= timeout;
                    elsif s_bitCount >= X"9" then
                        state <= count9bits;
                    else
                        state <= waitForStart;
                    end if;
                end if;     

            when count9bits =>  
                s_sel <= '0';
                if tdre = '0' then
                    state <= count9bits;
                elsif tdre = '1' then
                    state <= sendToUART;
                end if;   

            when sendToUART =>
                transmit <= '1';
                if tdre = '0' then
                    state <= start;
                else
                    state <= sendToUART;
                end if;

            when timeout =>
                --s_timeoutDetect <= '1';
                state <= start;

        end case;   
    end if;
end process n64RX;  
--timeoutDetect <= s_timeoutDetect;
bitcount <= s_bitCount;
echoSig <= s_echoSig;
sel <= s_sel;
data <= s_data(4 downto 1);

end N64RX;

Итак, есть идеи? Советы по отладке? Советы по кодированию конечных автоматов?

В то же время, я буду продолжать играть с этим (у меня это будет в конечном итоге)! Помоги мне Stack Exchange, ты моя единственная надежда!

редактировать

Еще одно открытие в моей отладке - состояния переместятся с waitForStart обратно на waitForStop. Каждому состоянию я присвоил значение с waitForStart, равным «5», и waitForStop, равным «4». Смотрите изображение ниже: введите описание изображения здесь

Ник Уильямс
источник
1
В вашем первом блоке case есть строка "s_bitCount <= X" 0 ";" Это Х опечатка?
Трэвисбартли
@ trav1s Нет, это "X" обозначает шестнадцатеричное. Таким образом, X «0» на самом деле «0000» в двоичном формате.
Ник Уильямс
1
Я получил пару ошибок при запуске кода через линтера. Сигналы N64RXD и tdre не должны использоваться в списке чувствительности последовательного процесса, строка 36.
travisbartley
1
@ trav1s Спасибо за указатель, я удалил эти параметры; Вы правы, это не нужно. У меня все еще есть проблема, к сожалению. С помощью прицела я добавил сигналы, чтобы определить, в каком состоянии я нахожусь. По какой-то причине FPGA переходит от «waitForStart» обратно к «waitForStop» без промежуточного состояния! Вот почему он не считается, потому что FPGA не достигает состояния, в котором он считает бит. Кажется, проблема в «прыжке назад».
Ник Уильямс
1
Но переход «waitForStart» -> «waitForStop» недопустим. Нет способа совершить этот прыжок за один цикл. Проверьте очень внимательно, чтобы убедиться, что между ними нет очень краткого состояния. В противном случае должна быть аппаратная ошибка или ошибка синхронизации.
Трэвисбартли

Ответы:

9

Я не вижу синхронизатора в строке данных rx.

Все асинхронные входы должны быть синхронизированы с тактовой частотой дискретизации. Для этого есть несколько причин: метастабильность и маршрутизация. Это разные проблемы, но они взаимосвязаны.

Для распространения сигналов через матрицу ПЛИС требуется время. Сеть часов внутри ПЛИС предназначена для компенсации этих задержек "путешествия", так что все триггеры в ПЛИС видят часы в один и тот же момент. Обычная сеть маршрутизации не имеет этого, и вместо этого полагается на правило, согласно которому все сигналы должны быть стабильными в течение небольшого времени до изменения часов и оставаться стабильными в течение небольшого времени после изменения часов. Эти маленькие кусочки времени известны как время установки и удержания для данного триггера. Компонент place и route в цепочке инструментов очень хорошо понимает задержки маршрутизации для конкретного устройства и делает базовое предположение, что сигнал не нарушает настройки и время удержания триггеров в FPGA.

Если у вас есть сигналы, которые не синхронизированы с тактовыми импульсами, вы можете оказаться в ситуации, когда один триггер видит «старое» значение сигнала, поскольку новое значение не успело распространиться. Теперь вы находитесь в нежелательной ситуации, когда логика, смотрящая на один и тот же сигнал, видит два разных значения. Это может привести к неправильной работе, сбоям конечных автоматов и всевозможным ошибкам в диагностике.

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

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

Решение заключается в синхронизации сигнала. На самом базовом уровне это означает, что вы используете цепочку триггеров для захвата ввода. Любой метастабильный уровень, который мог быть захвачен первым триггером и каким-то образом его удалось разобрать, получает еще один шанс для разрешения, прежде чем он достигнет вашей сложной логики. Два триггера обычно более чем достаточно для синхронизации входов.

Базовый синхронизатор выглядит так:

entity sync_2ff is
port (
    async_in : in std_logic;
    clk : in std_logic;
    rst : in std_logic;
    sync_out : out std_logic
);
end;

architecture a of sync_2ff is
begin

signal ff1, ff2: std_logic;

-- It's nice to let the synthesizer know what you're doing. Altera's way of doing it as follows:
ATTRIBUTE altera_attribute : string;
ATTRIBUTE altera_attribute OF ff1 : signal is "-name SYNCHRONIZER_IDENTIFICATION ""FORCED IF ASYNCHRONOUS""";
ATTRIBUTE altera_attribute OF a : architecture is "-name SDC_STATEMENT ""set_false_path -to *|sync_2ff:*|ff1 """;

-- also set the 'preserve' attribute to ff1 and ff2 so the synthesis tool doesn't optimize them away
ATTRIBUTE preserve: boolean;
ATTRIBUTE preserve OF ff1: signal IS true;
ATTRIBUTE preserve OF ff2: signal IS true;

synchronizer: process(clk, rst)
begin
if rst = '1' then
    ff1 <= '0';
    ff2 <= '0';
else if rising_edge(clk) then
    ff1 <= async_in;
    ff2 <= ff1;
    sync_out <= ff2;
end if;
end process synchronizer;
end sync_2ff;

Подключите физический вывод для строки данных rx контроллера N64 ко входу async_in синхронизатора и подключите сигнал sync_out к входу rxd вашего UART.

Несинхронизированные сигналы могут вызвать странные проблемы. Убедитесь, что любой вход, подключенный к элементу FPGA, который не синхронизирован с часами процесса, считывающего сигнал, синхронизирован. Это включает в себя кнопки, сигналы UART «rx» и «cts» ... все, что не синхронизировано с часами, которые FPGA использует для выборки сигнала.

(За исключением: я написал страницу по адресу www.mixdown.ca/n64dev много лет назад. Я только что понял, что сломал ссылку, когда последний раз обновлял сайт, и исправлю ее утром, когда вернусь за компьютер. Я понятия не имел, так много людей использовали эту страницу!)

akohlsmith
источник
Спасибо за отличный и исчерпывающий ответ! Я собираюсь попробовать и сделать мою машину более надежной.
Ник Уильямс
2
На самом деле это имеет очень мало общего с метастабильностью (хотя это тоже проблема), и все, что связано с различными задержками пути от асинхронного ввода до различных FF, которые содержат биты переменной состояния.
Дэйв Твид
Вы правы, @DaveTweed; Я склонен смешивать их вместе, и это неправильное мышление.
akohlsmith
Я отредактировал свой ответ, чтобы учесть комментарии @ DaveTweed.
akohlsmith
1
@akohlsmith Удивительно! Я добавил синхронизатор, и это было решением. Кроме того, это невероятное совпадение, что вы написали страницу микширования; Я нашел кучу ресурсов по протоколу N64, которые ссылались на эту статью, и я был разочарован, что ссылка была разорвана. Спасибо за исправление.
Ник Уильямс
6

Ваша проблема в том, что вы используете несинхронизированные сигналы для принятия решений в вашем автомате. Вы должны подавать все эти внешние сигналы через синхронизаторы с двойным FF, прежде чем использовать их в автомате.

Это тонкая проблема с конечными автоматами, которая может возникнуть при любом переходе состояния, который включает в себя изменение двух или более битов в переменной состояния. Если вы используете несинхронизированный вход, один из битов может измениться, а другой не изменится. Это приведет вас к другому состоянию, чем предполагалось, и это может быть или не быть законным государством.

Это последнее утверждение является причиной того, что у вас также всегда должен быть регистр по умолчанию (в VHDL, when others => ...) в вашем выражении состояния машины состояний, которое переводит вас из любого недопустимого состояния в легальное.

Дэйв Твид
источник
Да, это был вывод, который я собирался изолировать, но я не хотел переходить к нему, прежде чем получить достаточно информации ...
travisbartley
1
Блин, ты меня обыграл. Я виноват в том, что все это печатал на планшете. :-)
akohlsmith
@akohlsmith, быть самым быстрым оружием на востоке - не единственное, что имеет значение при ответе. Ваш ответ полезен и, очевидно, не был обманом, так как вы опубликовали его вскоре после этого.
Трэвисбартли
Раньше я думал, что when others =>это помогает, но оказывается, что оно не даст вам того, на что вы претендуете (под любым синтезатором, который я использовал), если вы не добавите атрибуты, чтобы гарантировать, что синтезатор понимает, что вы хотите «безопасный» конечный автомат. Нормальным поведением является оптимизация до «горячего» представления, а не обеспечение логики восстановления. См xilinx.com/support/answers/40093.html и synopsys.com/Company/Publications/SynopsysInsight/Pages/... , например.
Мартин Томпсон
Вот Это Да! Это отличный совет, и он работал как шарм.
Ник Уильямс