Pull to refresh

Comments 27

Разбиение на 1/2/3 процесса/блока, как у вас показано в начале текста — вещь чисто техническая и ни на что не влияющая. Более глубокое различие в концепции описания — это выбор между формированием внутри КА только набора управляющих сигналов (как у вас в примере с 3, а на самом деле 5 блоками), либо интеграция более сложной логики вовнутрь описания (как в примере с 1 блоком). И вот здесь выбор может быть более сложным в зависимости от сложности и архитектуры создаваемого решения. Хотя мне тоже, признаюсь, более удобным кажется слепить все вместе ;)
Я пытаюсь говорить про разницу между 2(3) и 1 частным описаниями автоматов. Технически, конечно, можно комбинаторный always блок написать через
assign r = (c1) ? a  : 
           (с2) ? b  : 
           ....  

но это жестоко).
Поэтому 2 частному автомату я определяю 2 always блока. Я не пытаюсь числом always блоков определить тип описания, скорее тип описания определяет минимальный набор блоков.

А всегда ли удобно параллельные изменения состояний сигналов синхронизировать через FSM?
Мне часто наглядней задавать изменение выходных сигналов параллельно. При этом добавляя некоторые сигналы-флаги для синхронизации сигналов вместо FSM. Такое описание ближе к конечному результату синтеза, в котором все выполняется параллельно, и при этом нет ограничений последовательного подхода, базирующегося только на изменении состояний FSM.

В существенном количестве случаев можно обойтись без организации FSM, там где подойдет реализация в 1 always block. Тк FSM будет нас ограничивать в подходе с единой синхронизации состояний (чем-то сходным с софтверным подходом, где все последовательно), и не позволит использовать «распределенную синхронизацию» только где надо.

Те может глубинная идея в книге была в том, что там где можно описать в 1 always block FSM сам по себе и не нужен, тк ограничивает.

Все может быть, я прочел ту идею, которую прочел. И поделился тем о чем подумал. Я не настаиваю ни на какой интерпритации:)

Есть FSM Милли и FSM Мура, а сколько брать процессов для их описания — уже формальности.
Я не затрагивал виды автоматов, кстати, их больше двух. Я сосредоточился именно на способе их описания. Так что может и формальность, но именно на формальности я и сосредоточился)
По типу автоматов — в статье описан классический алгоритм Мура (автомат Мили можно свести к Мура), поскольку язык верилог под конструкцией always понимает элемент памяти типа мастер-слейв (вообще и защелку тоже, но в контексте поста — триггер), и другие типы автоматов (которые нельзя свести к Мура) таким кодом не опишешь. Можно было бы для расширения кругозора описать еще автомат Хаффмана, но тогда без always, поскольку он асинхронен (использует элемент памяти на линии задержки).

Было бы полезно не просто запостить некий верилог-код автомата, а показать как сделать так, чтобы синтезаторы synopsys/cadence понимали, что это за автомат. Зачем? Да потому что в тексте нигде нет ни слова про минимизацию автоматов, а так хоть тулам на откуп отдать можно было бы. Поэтому статья Sunburst design (которую автор критикует в начале) реально рулит, а этот пост — нет.
Если сделать все что вы написали, то получиться еще одна статья с которой мы начали. Мне жаль, что мне не удалось донести до Вас основную идею моего поста :)
А в чем идея? В стиле описания? Если бы Вы показали, что синтезатор два олвейз-блока минимизирует лучше чем один или три олвейз-блока — был бы аргумент. Вместо этого ведутся рассуждения о числе строк кода. Вы знаете, что индусам платят за число строк кода? Они сделают из Вашей публикации диаметрально противоположный вывод. О вкусах, как говорят, не спорят.
Я думаю не стоит сокрушаться, что я не написал в статье то, что вы хотели бы видеть. Это же дает вам огромные возможности для своих статей :)

Если говорить про оптимизацию (по разным параметрам) то сильнее влияют способы кодирования состояний и типы автоматов, чем число always блоков при их описании.

И да, я пытаюсь говорить о стилях и строках, потому что считаю немаловажными параметрами качества кода: его читаемость, управляемость, поддерживаемость.
Статья все же рассказывает о том, о чем и была задумана, а именно:
Стиль описания конечного автомата как образ мышления

ИМХО, тут важно одно — читаемость кода. Правильно сказано, что для разных задач используются разные способы описания, хоть они и дадут в синтезе одинаковый результат. Дело вкуса…
Спасибо автору за статью, очень интересно! Во-первых, как я понимаю код автора содержит генерацию латчей, что не очень хорошо, и даже плохо ;) Код, по хорошему, надо исправить. Например, для первого примера с 2мя блоками в начале второго блока должна быть строка «NextState <= State;» Иначе будет latch. По крайней мере, на VHDL это так. Во-вторых, множественны параллельные процессы необходимы для более сложных алгоритмов. Например, в примере с памятью, события от модуля памяти могут приходить в другом clock domain, что требует дополнительных процессов для синхронизации доменов. В-третьих, можно заметить что описанная FSM всегда синхронизированна по clk, что не оптимально по скорости. Для оптимизации нужно применять комбинаторную логику с синхронизацией не по клоку а по событиям, что конечно, порождает большое количество параллельных процессов. Рекомендую обратиться к коду примеров Xilinx что бы «ужаснуться» от количеству процессов даже в простом коде ;)
Пожалуйста, рад что вам понравилось.
Во-первых, как я понимаю код автора содержит генерацию латчей, что не очень хорошо, и даже плохо

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

И на ВХДЛ тоже.

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

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

В-третьих, можно заметить что описанная FSM всегда синхронизированна по clk, что не оптимально по скорости.

Неужели вы предлагаете на клоковые входы триггеров заводить комбинаторные сигналы. И это в FPGA? И это для увеличения скорости?
Рекомендую обратиться к коду примеров Xilinx что бы «ужаснуться» от количеству процессов даже в простом коде ;)

Вы либо только начинаете, либо уже так продвинуты в описании железа, что на моем уровне мне трудно понять что вы имеете в виду %). Но все же я больше склоняюсь к первому, чем ко второму).
В любом случае, спасибо за мнение :)

Как по мне, так описание одни always блоком или в одном process( в терминологии VHDL) порождает на выходе FSM лишние и, самое главное, никому не нужные триггеры.
Если брать реализацию классического автомата то это 3 always блока.
Первый always блок — последовательная логика.
В нем FSM приобретает начальное значение по сбросу и в этом процессе в регистр состояния записывается новое состояние автомата.

Второй always блок — комбинационная логика переходов.
Здесь вычисляется следующее состояние автомата.

Третий always блок- выходная логика автомата.
В нем в зависимости от текущего состояния автомата формируются выходные сигналы. Если сигналы зависят только от текущего состояние — это это автомата Мура. Если же сигналы зависят как от текущего состояния автомата, так и от входных воздействий — то это автомат Мили.

При этом если последние 2 always блока объединить в один, то не изменится ровным счетом ничего.
Писать автомат в 2 always блоках или в 3 — вопрос чисто философский. Я предпочитаю 3 блока. Для меня так автомат нагляднее.

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

Я бы данную большую FSM разбил на 2 отдельных файла.
Первый файл — FSM, выдающая команды памяти.
Второй файл — контроллер задержек, который анализирует команды памяти, и отсчитывает необходимые задержки и выдает первой FSM данные о статусе задержек. Первая FSM на основании этих задержек и логики переходов меняет своё состояние.
В этом случае, видимый код сокращается в 2 раза. И самое главное становится более наглядной работа автомата.
Я вообще считаю, что большие автоматы, надо дробить на более маленькие и простые. И порой после такого деления работа всей системы становится простой и понятной. И немаловажным фактором является то, что отлаживать такие автоматы проще чем один большой и сложный
Писать автомат в 2 always блоках или в 3 — вопрос чисто философский

Нет, если мы говорим не про формальное разделение на блоки, а про функциональное. Третий always блок для формирования регистрового, а не комбинационного выхода. И это не философия.

порождает на выходе FSM лишние и, самое главное, никому не нужные триггеры.
До тех пор пока выход автомата не зашел на какой-либо асинхронный сброс или подобное…

В Вашей задачи получился большой автомат только из-за того, что неправильно разделена задача.

Я бы не называл данный автомат в 7 состояний большим, но интересно было бы посмотреть вашу реализацию. Пока мне не очевидно как предлагаемое Вами разбиение сделает работу более наглядной.
Нет, если мы говорим не про формальное разделение на блоки, а про функциональное. Третий always блок для формирования регистрового, а не комбинационного выхода. И это не философия.

Редкий случай когда нужны регистровые выходы — это размещение выходных сигналов в fsat output регистры, для более полного управления зажержками этих сигналов путем задания временных ограничений. Но для этого совсем необязательно применять регистровые выходы FSM.

До тех пор пока выход автомата не зашел на какой-либо асинхронный сброс или подобное…

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

Я бы не называл данный автомат в 7 состояний большим, но интересно было бы посмотреть вашу реализацию. Пока мне не очевидно как предлагаемое Вами разбиение сделает работу более наглядной.

На скорую руку сделал проект, как я его вижу. К сожалению на таком простом примере преимущества моего подхода показать тяжело. Но в более сложных контроллерах. Например контроллерах SDRAM все преимущества такого подхода налицо.
Вот так вот выглядит структура контроллера:
<img src="ARCH.PNG" alt=«image»/>
Контроллер задержек — это фактически обычный счетчик с синхронной загрузкой:
<img src="CNT.PNG" alt=«image»/>
Код выглядит так (писал на VHDL на скорую руку):
library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    
entity CMD_CTRL is
    generic
    (
        READ_SETUP   : natural  := 5;
        READ_PULSE   : natural  := 3;
        READ_HOLD    : natural  := 1;
        WRITE_SETUP  : natural  := 5;
        WRITE_PULSE  : natural  := 3;
        WRITE_HOLD   : natural  := 1
    );
	port
    (
        clk           : in  std_logic;
        reset         : in  std_logic;
        w_strb        : in  std_logic; 
        r_strb        : in  std_logic;
        
        s_waddress    : in  std_logic_vector(7 downto 0);
        s_raddress    : in  std_logic_vector(7 downto 0);
        s_data_to     : in  std_logic_vector(7 downto 0);
        s_data_from   : out std_logic_vector(7 downto 0);
        done          : out std_logic;
        
        --------------------- Интерфейс с контроллером задержек ----------------------
        delay_done    : in  std_logic;
        delay_start   : out std_logic;
        delay_data    : out natural range 0 to 7;
        
         --------------------- Интерфейс с памятью ----------------------
        m_data_to     : out std_logic_vector(7 downto 0);
        m_data_from   : in  std_logic_vector(7 downto 0);
        m_address     : out std_logic_vector(7 downto 0);
        cs_n          : out std_logic;
        oe_n          : out std_logic;
        we            : out std_logic
    );
end entity;

architecture rtl of CMD_CTRL is
    type state_type is 
    (
        IDLE,                                              -- Состояние записи данных в 0 буфер 
        PREPARE_READ,                                     -- Состояние записи данных в 1 буфер 
        READ,
        END_READ,
        PREPARE_WRITE,
        WRITE,
        END_WRITE
    );
    signal pres_state, next_state : state_type := IDLE;  
    attribute syn_encoding : string;                        -- атрибуты синтеза
    attribute syn_encoding of state_type : type is "safe";  -- создать машину состойний, выходящую из ошибочных состояний
    
    signal addr_latch : std_logic_vector(7 downto 0) := (others => '0');
    signal data_latch : std_logic_vector(7 downto 0) := (others => '0');
    signal read_ena   : std_logic;
    signal dellay_cnt : unsigned(5 downto 0) := (others => '0');
    signal dellay     : std_logic_vector(2 downto 0);
begin

    --======================================================
    -- Смена состояний автомата на вычесленное состояние
    --======================================================
    next_state_proc : process(clk, reset)
    begin  
        if (reset = '1') then
            pres_state <= IDLE;
        elsif (rising_edge(clk)) then
            pres_state <= next_state;            
        end if;
    end process;
    
    --======================================================
    -- Процес вычесления следующего состояния автомата
    --======================================================
    next_state_logic : process(all)
    begin
        case pres_state is
            when IDLE            =>
                                    next_state <= IDLE;
                                    if (w_strb = '1') then 
                                        next_state <= PREPARE_WRITE;
                                    end if;
                                    
                                    if (r_strb = '1') then
                                        next_state <= PREPARE_READ;
                                    end if;
            --====================================================================================================
            when PREPARE_READ    =>
                                    next_state <= PREPARE_READ;
                                    if (delay_done = '1') then 
                                        next_state <= READ;
                                    end if;
            --====================================================================================================
            when READ            =>
                                    next_state <= READ;
                                    if (delay_done = '1') then 
                                        next_state <= END_READ;
                                    end if;
            --====================================================================================================
            when END_READ        =>
                                    next_state <= END_READ;
                                    if (delay_done = '1') then 
                                        next_state <= IDLE;
                                    end if;
            --====================================================================================================
            when PREPARE_WRITE   =>
                                    next_state <= PREPARE_WRITE;
                                    if (delay_done = '1') then 
                                        next_state <= WRITE;
                                    end if;
            --====================================================================================================
            when WRITE           =>
                                    next_state <= WRITE;
                                    if (delay_done = '1') then 
                                        next_state <= END_WRITE;
                                    end if;
            --====================================================================================================
            when END_WRITE       =>
                                    next_state <= END_WRITE;
                                    if (delay_done = '1') then 
                                        next_state <= IDLE;
                                    end if;
            
            when others         => null;
        end case;
    end process;

    --======================================================
    -- Процесс выходных данных
    --======================================================
    output_data_logic : process(all)
    begin
        cs_n        <= '1';
        oe_n        <= '1';
        we          <= '0';  
        m_address   <= (others => '-');
        done        <= '0';
        read_ena    <= '0';
        delay_start <= '0';
        dellay      <= (others => '-');
        case pres_state is
            when IDLE            =>
                                    cs_n <= '1';
                                    oe_n <= '1';
                                    we   <= '0'; 
                                    
                                    
                                    if (w_strb = '1') then 
                                        delay_start <= '1';
                                        dellay <= std_logic_vector(to_unsigned(WRITE_SETUP,3));
                                    end if;
                                    
                                    if (r_strb = '1') then
                                        delay_start <= '1';
                                        dellay <= std_logic_vector(to_unsigned(READ_SETUP,3));
                                    end if;
            --====================================================================================================
            when PREPARE_READ    =>
                                    cs_n <= '0';
                                    oe_n <= '1';
                                    we   <= '0'; 
                                    m_address <= addr_latch;
                                    
                                    if (delay_done = '1') then
                                        delay_start <= '1';
                                    end if;
                                    
                                    dellay <= std_logic_vector(to_unsigned(READ_PULSE,3));
                                    
            --====================================================================================================
            when READ            =>
                                    cs_n <= '0';
                                    oe_n <= '0';
                                    we   <= '0'; 
                                    
                                    if (delay_done = '1') then
                                        read_ena <= '1';
                                        delay_start <= '1';
                                    end if;
                                    m_address <= addr_latch;
                                    dellay <= std_logic_vector(to_unsigned(READ_HOLD,3));
            --====================================================================================================
            when END_READ        =>
                                    cs_n <= '0';
                                    oe_n <= '1';
                                    we   <= '0'; 
                                    if (delay_done = '1') then
                                        done <= '1';
                                        delay_start <= '1';
                                    end if;
                                    m_address <= addr_latch;
                                    
            --====================================================================================================
            when PREPARE_WRITE   =>
                                    cs_n <= '0';
                                    oe_n <= '1';
                                    we   <= '0'; 
                                    m_address   <= addr_latch;
                                    if (delay_done = '1') then
                                        delay_start <= '1';
                                    end if;
                                    dellay <= std_logic_vector(to_unsigned(WRITE_PULSE,3));
            --====================================================================================================
            when WRITE           =>
                                    cs_n <= '0';
                                    oe_n <= '1';
                                    we   <= '1'; 
                                    m_address <= addr_latch;
                                    if (delay_done = '1') then
                                        delay_start <= '1';
                                    end if;
                                    
                                    dellay <= std_logic_vector(to_unsigned(WRITE_HOLD,3));
            --====================================================================================================
            when END_WRITE       =>
                                    cs_n <= '0';
                                    oe_n <= '1';
                                    we   <= '0'; 
                                    if (delay_done = '1') then
                                        done <= '1';
                                        delay_start <= '1';
                                    end if;
                                    m_address <= addr_latch;
                                    
            when others         => null;
        end case;
    end process;
    
    
    m_data_to <= data_latch;
    delay_data <= to_integer(unsigned(dellay));
    
    --======================================================
    -- Защелкивание данных и адреса в регистры
    --======================================================
    addr_and_data_latch : process(clk, reset)
    begin  
        if (rising_edge(clk)) then
            if (w_strb = '1') then
                addr_latch <= s_waddress;
                data_latch <= s_data_to;
            end if;
            
            if (r_strb = '1') then
                addr_latch <= s_raddress;
            end if;
        end if;
    end process;
    --======================================================
    -- Чтение данных из памяти
    --======================================================
    read_data_from_memory : process(clk, reset)
    begin  
        if (rising_edge(clk)) then
            if (read_ena = '1') then
                s_data_from <= m_data_from;
            end if;
        end if;
    end process;
    
end architecture;




По занимаемым ресурсам — получается в 2 раза меньше.
По скорости почти на 100 MГц выше.
Если асинхронный сброс сделан правильно, то никаких проблем в схеме наблюдаться не должно. Если есть проблемы- значит сброс выполнен неверно!

Не понимаю как можно сделать правильно:) Комбинаторная схема во время переключения входных сигналов имеет полное право генерировать «иголки», которые вызовут сброс.
Все тоже самое верно и для комбинаторных выходов. Комбинаторные сигналы безопасно идут только внутри домена синхронных схем, как только вы их выдали наружу или в домен с другим клоком, вы потенциально породили проблемы. Если не сейчас, так завтра.

На скорую руку сделал проект, как я его вижу.

Понятно, спасибо. Я не стал отделять счетчик от автомата, чтобы не размывать суть повествования. Статья не про оптимизацию автоматов, а про разные виды записи. В любом случае спасибо за потраченное время! :)

П.С. По ресурсам, я думаю, не совсем чистый эксперимент, просто атрибутами вида автомата можно сильно менять результаты.
Не понимаю как можно сделать правильно:) Комбинаторная схема во время переключения входных сигналов имеет полное право генерировать «иголки», которые вызовут сброс.
Все тоже самое верно и для комбинаторных выходов. Комбинаторные сигналы безопасно идут только внутри домена синхронных схем, как только вы их выдали наружу или в домен с другим клоком, вы потенциально породили проблемы. Если не сейчас, так завтра.

А откуда там взяться иголкам? Вы сбрасываете асинхронно регистр состояний, а выходные сигналы зависят только от текущего состояния.
Абсолютно те-же иголки формируются при синхронном переключении состояний автомата.
Не понимаю я, как асинхронный сброс может вызвать сбой?
Конечно же при условии применения reset_bridge, который синхронно снимает сигнал сброса с домена схемы. Я поэтому и говорил, что сброс должен быть заведен правильно ^_^

Ну если уж так хочется — формируйте на триггере сигнал data_valid — строб сопровождающий правильные данные. И сбрасывайте этот сигнал вместе с автоматом. Тогда при отсутствии этого сигнала нам вообще не важно, что в этот момент происходит на шине данных например.

Комбинаторные сигналы безопасно идут только внутри домена синхронных схем

Пересечение клоковых доменов — это вообще отдельная тема для дискуссий )
Конечно же при условии применения reset_bridge, который синхронно снимает сигнал сброса с домена схемы. Я поэтому и говорил, что сброс должен быть заведен правильно

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

Абсолютно те-же иголки формируются при синхронном переключении состояний автомата.

А вот это неверное утверждение.

например:
...
//-----------------------------------------------
assign next_cnt = cnt + 1'b1;
always @(posedge clk)
  cnt <= next_cnt;

//-----------------------------------------------
assign out_a = (cnt != 0) ? 1'b1 : 1'b0;
always @(posedge clk)
  out_s <= (next_cnt != 0) ? 1'b1 : 1'b0;
...

на сигнале out_s никогда не будет иголок, как могут быть на сигнале out_a. Подщелкивать выходные сигналы является хорошим тоном, даже в асиках, которые очень любят комбинаторику :).

А вот это неверное утверждение.

Секундочку!

Иголки — это следствие того, что сигналы какого-нибудь счетчика, например, переключаются не мгновенно, а с некоторой разбежкой (единицы наносекунд, а порой и сотни пикосекунд если брать современные FPGA). И из-за этого у нас и образуются иголки.

Но когда мы говорим о FPGA мы подразумеваем концепцию синхронного проектирования. И данные с этого счетчика (или сигнал с компаратора данных счетчика) будут защелкнуты фронтом синхрочастоты. А раз так, то у нас есть временной интервал Tsetup + Thold в течении которого данные должны быть стабильны.

Так вот иголки, которые возникают — они возникают за пределами этого временного окна. Фактически на границах сигнала. Если эти иголки попали в этот временной интервал — значит схема работает на слишком большой частоте.

Я вот этого не могу понять, откуда вообще могут появиться иголки?
Но когда мы говорим о FPGA мы подразумеваем концепцию синхронного проектирования

Это очень сильное ограничение :).

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

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

По поводу иголок (они же глитчи, гонки, или состязания), они присутствуют в практически любой комбинационной схеме, за исключением некоторых видов специального кодирования сигнала (к примеру — dual rail four phases).
несомненно, при организации любого сброса выход из него должен быть синхронным. Но проблемы именно со входом.
А можно пример, где иголки приводят к проблемам?
Разумеется мы не берем в расчет «кривые» проекты, где, например, выход комбинаторной логики без применения специальных методов подавления иголок заводится на асинхронные входы.
Последние где я наступил:

Из памяти читается код безопасности и его контрольная сумма. Для считанного кода считается контрольная сумма и сравнивается со считанной из памяти. Это сравнение вырабатывает разрешение на работу.

То есть схематично:
MEM -> CS1
MEM -> Code -> Calc -> CS2
assign allow = (CS1 == CS2)? 1'b1: 1'b0;

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

Конечно, это можно списать на «кривость» проекта. И да это примитивная ошибка.

Но так это же проблема как раз неправильно заведенного сброса!
Тем более, что если этот сигнал асинхронный, то кто мешал отдельно этот сигнал пропустить через триггер, для удаления иголок и потом уже применить как сброс?
Сброс должен был быть асинхронным чтобы реагировать на события даже в отсутствии клока. Источников событий было несколько в том числе и этот сигнал.

то кто мешал отдельно этот сигнал пропустить через триггер

Именно с этим сигналом так и было сделано. Поскольку его порождает тактируемая логика его можно было пропустить через триггер.

Я просто хочу отметить, что часто внутри синхронных дизайнов мы начинаем считать комбинаторные сигналы неизменными между клоками, как выходы триггеров. Так происходит с выходами автоматов описанных в 2 блока, которые по сути комбинаторные. И пока они управляют внутри схемы, работают с тактируемыми ее участками все Ок. Но при реюзе или поддержке проекта это может вылезти боком. Как и получилось с этим сигналом, он же использовался на самом деле много где, но всегда синхронно, и его асинхронная суть потерялась %)
Sign up to leave a comment.

Articles