Если у вас есть опыт создания ПО и вы хотите познакомиться с проектированием цифровых логических схем (digital design), то одна из первых вещей, которые вам нужно понять, — это концепция тактов. Она раздражает многих программных инженеров, начинающих HDL-проектирование. Без использования тактов они могут превратить HDL в язык программирования с $display, if и циклами for, как в любом другом языке. Но при этом такты, которые новички игнорируют, — зачастую один из основополагающих элементов при проектировании любых цифровых логических схем.


Ярче всего эта проблема проявляется именно при рассмотрении первых схем, созданных начинающими HDL-разработчиками. Я недавно общался с некоторыми из них. Новички опубликовали свои вопросы на форумах, которые я читаю. Когда я проанализировал то, что они делают, от увиденного волосы встали дыбом.


Например, один из студентов попросил объяснить, почему никого в сети не заинтересовала его HDL-реализация AES. Не стану смущать его, приводить ссылку на проект или имя его создателя. Вместо этого я буду называть его студентом. (Нет, я не профессор.) Так вот, этот студент создал Verilog-схему, в которой AES-шифрование выполняется в течение не одного раунда, а каждый раунд, с комбинаторной логикой без тактов. Не помню, какой именно AES он применил, 128, 192 или 256, но AES требует от 10 до 14 раундов. В симуляторе его движок шифрования работал идеально, при этом на шифрование/дешифрование своих данных он использовал только один такт. Студент гордился своей работой, но не мог понять, почему те, кто её смотрел, говорили ему, что он мыслит как программный инженер, а не аппаратный.


Иллюстрация 1. Программное обеспечение последовательно



Теперь у меня есть возможность дать советы программным инженерам вроде того студента. Многие из них обращаются с HDL как ещё с одним языком для написания приложений. Имея опыт программирования, они берут основы из любого софтверного языка программирования — как объявлять переменные, как сделать выражение «если», оператор выбора, как писать циклы и т. д., — а затем пишут код как компьютерную программу, в которой всё выполняется последовательно, при этом полностью игнорируя реалии проектирования цифровых логических схем, где всё происходит параллельно.


Иногда эти программисты находят симулятор вроде Verilator, iverilog или EDA playground. Тогда они используют в своей логике кучу команд $display, обращаясь с ними, как с последовательными printf, заставляя код работать без применения тактов. Затем их схемы «работают» в симуляторе с использованием одной лишь комбинаторной логики.


И потом эти студенты описывают мне свои схемы и объясняют, что они «без тактов».


Знаете что?


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


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


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


Давайте пока обсудим такт и важность постройки и проектирования вашей логики вокруг него.


Урок № 1: аппаратная архитектура — это параллельная архитектура


Первая и, вероятно, самая трудная часть изучения аппаратного проектирования заключается в том, чтобы осознать, что вся аппаратная архитектура параллельна. Ничто не выполняется последовательно, как одна инструкция за другой (см. иллюстрацию 1) в компьютере. На самом деле всё происходит за раз, как на иллюстрации 2.


Иллюстрация 2. Аппаратная логика работает параллельно



Это многое меняет.


Иллюстрация 3. Программный цикл



Первое, что должно измениться, — сам разработчик. Научитесь мыслить параллельно.


Хороший пример, иллюстрирующий это различие, — аппаратный цикл.


В ПО цикл состоит из серии инструкций, как на иллюстрации 3. Они создают набор начальных условий. Затем в цикле применяется логика. Для определения этой логики используется переменная цикла, которая часто инкрементируется. Пока эта переменная не достигнет состояния прерывания, процессор будет циклически повторять инструкции и логику. Чем больше раз прогоняется цикл, тем дольше выполняется программа.


Аппаратные циклы на базе HDL работают совсем не так. Средство синтеза (synthesis tool) HDL использует описание цикла для создания нескольких копий логики, все они выполняются параллельно. Нет нужды синтезировать логику, используемую для создания цикла, — например индекс, инкрементирование этого индекса, сравнение индекса с финальным состоянием и т. д., — поэтому её обычно убирают. Более того, поскольку средство синтеза создаёт физические соединения и логические блоки, количество прохождений цикла не может изменяться после завершения синтеза.


Получающаяся структура показана на иллюстрации 4. Она очень отличается от структуры программного цикла на иллюстрации 3.


Иллюстрация 4. Цикл, сгенерированный HDL



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


Но… теперь мы снова вернулись к концепции цикла.


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


Почему важен такт


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


Эта мысль изображена на графике. Давайте поместим сверху входные данные для нашего алгоритма, в середине — логику, а внизу — выходные данные. Ось времени отложим сверху вниз, от одного такта до другого.


Иллюстрация 5. Выполнение логики занимает время, три операции



На иллюстрации 5 показано несколько операций (сложение, умножение) и несколько раундов применения AES, хотя для нашего примера это могут быть раунды любого другого алгоритма. Вертикальный размер блоков характеризует длительность каждой операции. Кроме того, операции, зависящие от других операций, идут друг за другом. То есть если вы хотите в пределах такта выполнить много раундов применения AES, то имейте в виду, что второй раунд не начнётся до тех пор, пока не завершится первый. Следовательно, внедрение такой логики увеличит длительность между тиками тактов и замедлит общую тактовую частоту.


Посмотрите на розовые блоки.


Они отражают потери активности вашей аппаратной схемы (hardware circuit) — время, которое можно было бы потратить на выполнение каких-то операций. Но поскольку вы решили дождаться завершения такта или ждёте, чтобы сначала обработать входные данные, то вы ничего не можете делать. Например, на этом графике умножение длится не больше одного раунда применения AES, как и сложение. Однако вы не можете ничего сделать с результатами этих двух операций, пока выполняются вычисления AES, потому что для получения их следующих входных данных нужно дождаться следующего такта. То есть розовые блоки — это время простоя электронной схемы. Причём в данном случае оно увеличено из-за того, что раунды применения AES отдаляют следующий такт. Так что такая схема не сможет использовать всех возможностей оборудования.


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


Иллюстрация 6. Разбиение операций для ускорения тактов



После разбиения операции на более мелкие операции, каждая из которых может быть выполнена между тиками тактов, потери активности сильно уменьшаются. Причём вместо шифрования только одного блока данных за раз мы можем превратить алгоритм шифрования в конвейер. Получившаяся логика не будет шифровать одиночный блок быстрее, чем на иллюстрации 5, но если поддерживать наполненность конвейера, то это в 10—14 раз увеличит скорость AES-шифрования.


Это следствие более качественной архитектуры.


Можно ли сделать ещё лучше? Да! Если вы знакомы с AES, то знаете, что каждый раунд состоит из дискретных шагов. Их тоже можно раскидать, ещё больше увеличив скорость такта, пока выполнение логики AES-раунда не станет занимать меньше времени, чем умножение. Это увеличит количество сложений и умножений, которые вы можете выполнить, так что разложение движка шифрования на микроконвейеры позволит пропускать за такт ещё больше данных.


Неплохо.


Также на иллюстрации 6 показана парочка других вещей.


Во-первых, будем считать стрелки задержками при маршрутизации (routing delays). График не масштабный, это лишь иллюстрация для отвлечённой дискуссии. Каждая часть логики должна получать результат предыдущей части логики. Это означает, что даже если какой-то части логики не требуется времени для выполнения — как если бы просто менялся порядок проводов, — то переход логики с одного конца микросхемы к другому всё же займёт время. Следовательно, даже если вы максимально упрощаете свои операции, всё равно будут задержки на перемещение данных.


Во-вторых, вы могли заметить, что ни одна стрелка не начинается с начала такта. И ни одна не доходит до следующего такта. Это сделано для иллюстрирования концепции времени установки и удержания (setup and hold timing). Триггеры — это структуры, которые удерживают и синхронизируют ваши данные в такте. Им нужно время до начала такта, чтобы данные стали постоянны и определены. Хотя многие считают, что такт начинается мгновенно, на самом деле это не так. В разных частях микросхемы он начинается в разное время. И это тоже требует буфера между операциями.


К каким заключениям можно прийти в результате этого урока?


  1. Выполнение логики требует времени.
  2. Чем больше логики, тем больше нужно времени.
  3. Скорость такта ограничена количеством времени, которое нужно для выполнения логики, находящейся между тиками тактов (плюс задержки при маршрутизации, время установки и удержания, неопределённость начала такта и т. д.).
    Чем больше логики в тактах, тем ниже тактовая частота.
  4. Скорость быстрейшей операции будет ограничена скоростью такта, необходимой для выполнения самой медленной операции.
    Например, вышеописанная операция сложения. Она могла выполняться быстрее умножения и любого одиночного раунда применения AES, однако ей приходилось ждать выполнения остальной логики в схеме.
  5. Существует аппаратное ограничение скорости такта. Какое-то время уходит даже на операции, не требующие логики.

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


Сколько логики помещать в тактах?


Теперь вы знаете, что вам необходимо работать с тактами. Как вы измените или построите свою схему в свете этой информации? Правильный ответ: вы ограничите количество логики в тактах. Но насколько ограничить и как это понять?


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


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


Например, я хочу построить схему с тактовой частотой 100 МГц с деталями серии Xilinx 7. Эти схемы потом обычно работают на 80 МГц и на Spartan-6 либо на 50 МГц и на iCE40 — хотя это и не строгие сочетания. На одном чипе пойдёт нормально, другой чип окажется избыточно мощным, на третьем появятся проблемы с проверкой синхронизации (timing check).


Вот несколько примерных эвристических правил, относящихся к тактам. Поскольку это эвристика, вряд ли она применима для всех видов схем:


1. Обычно я могу в рамках такта выполнять 32-битное сложение с мультиплексированием 4—8 элементов.


Если будете использовать более быстрые такты, например 200 МГц, то вам может понадобиться отделить сложение от мультиплексора. Самый длинный путь ZipCPU начинается с выходных данных ALU и заканчивается входными данными ALU. Звучит просто. Даже соответствует вышеописанной эвристике. Проблема, с которой борется ZipCPU на более высоких скоростях, заключается в маршрутизации выходных данных обратно в ALU.


Давайте разберём маршрут: после ALU логический путь сначала идёт через четырёхсторонний мультиплексор, чтобы решить, чьи выходные данные нужно записать обратно — из ALU, памяти или операции деления. Затем записанный результат передаётся в схему обхода (bypass circuit), чтобы определить, нужно ли немедленно передавать его в ALU в качестве одних из двух входных данных. Только в конце этого мультиплексора и обхода наконец-то выполняется ALU-операция и мультиплексор. То есть на каждом этапе логический путь может пройти через ALU. Но благодаря конструкции ZipCPU любые такты, вставленные в логический путь, пропорционально замедлят выполнение ZipCPU. Это означает, что самый длинный путь наверняка на время останется самым длинным путём ZipCPU.


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


2. 16?16-битное умножение занимает только один такт. Иногда на каком-нибудь оборудовании я могу реализовать в одном такте 32?32-битные умножения. А на другом оборудовании придётся разбивать на части. Так что если мне когда-нибудь понадобится знаковое (signed) 32?32-битное умножение, я воспользуюсь конвейерной подпрограммой (pipelined routine), которую я сделал для таких случаев. Она содержит несколько вариантов умножения, что позволяет мне выбирать подходящие для текущего оборудования опции.


Ваше оборудование также может изначально поддерживать 18?18-битные умножения. Некоторые FPGA поддерживают умножение и накапливание в рамках одного оптимизированного аппаратного такта. Изучите своё оборудование, чтобы узнать, какие возможности вам доступны.


3. Любое блочное обращение к оперативной памяти (block RAM access) занимает один такт. Старайтесь не подстраивать индекс в ходе этого такта. Также избегайте в это время любых операций с выходным данными.


Хотя я утверждаю, что это хорошее правило, но мне приходилось успешно нарушать обе его части без (серьёзных) последствий при работе на 100 МГц на устройстве Xilinx 7 (у iCE40 с этим проблемы).


Например, ZipCPU считывает из своих регистров, добавляет непосредственный операнд к результату, а затем выбирает, должен ли результат быть регистр плюс константа, PC плюс константа либо регистр кода условия (condition code register) плюс константа — всё в одном такте.


Другой пример: долгое время Wishbone Scope определял адрес для считывания из своего буфера на основе того, выполняется ли чтение из памяти в ходе текущего такта. Разрыв этой зависимости потребовал добавления ещё одного такта задержки, так что текущая версия больше не нарушает это правило.


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


Следующие шаги


Возможно, лучший совет, что я могу напоследок дать новичкам-разработчикам FPGA, — это изучать HDL, параллельно практикуясь на реальном оборудовании, а не на одних лишь симуляторах. Инструменты, связанные с реальными аппаратными компонентами, позволят вам проверять код и необходимые тайминги. Кроме того, разрабатывать схемы для быстрых тактов — хорошо, но в аппаратном проектировании на этом свет клином не сошёлся.


Помните, аппаратная архитектура по своей природе параллельная. Всё начинается с такта.

Комментарии (29)


  1. nerudo
    11.10.2017 12:38
    +9

    Очень интересно. Остался один вопрос: mail.ru-то здесь причем?


    1. nothern_wind
      11.10.2017 17:32
      +9

      Mail.ru бар теперь и на FPGA)


    1. Dark_Purple
      12.10.2017 12:00

      Вы как буд-то не рады, ведь приличная статья.


      1. nerudo
        12.10.2017 12:20

        А вдруг маил.ру открывает новое направление и им нужны опытные сотрудники? Хотел прояснить вопрос ;)


  1. vlsergey
    11.10.2017 13:32
    +2

    «Нельзя просто так взять и...» распараллелить блочное шифрование (AES). В большей части режимов сцепления блоков (OFB, CFB, etc.) входной следующий блок недоступен до окончания шифрования предыдущего.

    Что ещё раз доказывает — не стоит браться за самостоятельное реализацию «простых и известных алгоритмов», пока не узнаешь хотя бы, что такое timing и вообще side-channel атаки.

    image


    1. Gryphon88
      11.10.2017 15:26

      Я правильно перевел «пока не станцует/объяснит танцем», или там какой-то фразеологизм?


      1. vlsergey
        11.10.2017 20:45

        Пока не придумает («создаст») имеющий поддающийся [смотрящим] чтению смысл танец. Как-то так.


      1. Gribs
        11.10.2017 22:03

        Я думаю тут речь идет о видео где различные алгоритмы сортировки объясняются с помощью танца. Ссылка не плэйлист: www.youtube.com/watch?v=CmPA7zE8mx0&list=PLuE79vNc5Wi6q34LsQcaJ7ISQ8uOyMaL_


    1. iDoka
      11.10.2017 20:40

      Ваше высказывание относительно режимов сцепления блоков (OFB, CFB, etc.) верно, однако ничего не мешает «распараллелить» (читай — конвейеризовать) сами раунды в рамках работы с одним блоком — другим словом: развернуть программный цикл.


      1. vlsergey
        11.10.2017 20:52

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

        Не уверен, что автор даже разобрался в AES, так как вот такие фразы вызывают большие вопросы:

        каждый раунд состоит из дискретных шагов. Их тоже можно раскидать, ещё больше увеличив скорость такта

        Тут явно идёт речь не про векторизацию шага внутри раунда, а про попытку раскидать сами шаги между потоками. Насколько я понимаю AES, это невозможно.


        1. khim
          11.10.2017 21:37

          Тут явно идёт речь не про векторизацию шага внутри раунда, а про попытку раскидать сами шаги между потоками.
          Нет, тут говорится о том, что каждый раунд AES состоит из подшагов (InvShiftRows, InvSubBytes и так далее). Если в каждом такте делать только один подшаг — то количество исполнительных блоков и потребных тактов станет больше, но зато каждый такт станет меньше. Привет Pentium4, короче…


      1. babylon
        13.10.2017 14:17

        iDoka это можно делать даже на псевдотредах. Было бы желание:)))


  1. old_bear
    11.10.2017 14:43
    -4

    Не надо таких статей.
    А то каждая домохозяйка любой программист сможет освоить правильный hdl и оставит без хлеба масла и икры честных hdl-дизайнеров.


    1. Danik-ik
      11.10.2017 16:22
      +2

      Не, умные программисты порадуются, что сидят на своём месте и будут больше уважать аппаратчиков. Как говорил мне когда-то один гитарный мастер: попробуешь сам — поймёшь, почему я так легко об этом рассказываю...


  1. IvanKor2017
    11.10.2017 14:46
    +2

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

    Утверждение в корне неверно. Используя полный базис логических функций («И», «ИЛИ», «НЕТ»)
    можно реализовать любую цифровую логическую схему. Такты вводят для других причин, как то по максимуму исключить «гонки» и проч. Как чел. который делал цифровые логические схемы еще на П416 из за неимения иных элементов, был крайне удивлён что при проектировании FPGA настойчиво навязывают тактирование, даже грешным делом подумал, что это за фуфло такое FPGA которое не может работать без тактов. И как чел. который много десятков лет программирует на всяких емембет и писюках могу сказать, что исенно здесь и выплывают такты, ибо команда за такт или более, а в железе такты как зайцу стоп сигнал.


    1. hardegor
      11.10.2017 16:25

      Просто без тактирования получается многовариантная система с точки зрения гонок и она сложно просчитывается, а с тактами становится понятнее и просчитывается элементарно. Что в общем-то автор и описал.


    1. babylon
      11.10.2017 16:49

      Такты сами по себе ничего не исключают, но они позволяют сделать рассинхронизацию по тактам. Это верно. Не надо противопоставлять такты схемам на базе ЛФ. Это некорректно.


    1. old_bear
      11.10.2017 17:04

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


      1. nothern_wind
        11.10.2017 17:41

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


    1. xFFFF
      11.10.2017 20:42

      Асинхронная логика — прошлый век!)


  1. y1trewq
    11.10.2017 14:46
    +5

    > Дело в том, что никакая цифровая логическая схема не может работать «без тактов»

    Это не так. Автоматы без памяти и асинхронные схемы вполне работают без «тактов».


  1. nothern_wind
    11.10.2017 17:30

    В начале был такт.


  1. kibb
    11.10.2017 17:52
    +1

    Оригинал:

    This “student” had created a Verilog design to do not one round of AES encryption, but every round, all in combinatorial logic with no clocks in between.


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


    Имеется в виду, что все операции происходили без разделения на шаги (раунды), в одном раунде. Вот зачем такой перевод? Просто бросили бы ссылку на оригинал.


  1. lexa_shkvarka
    11.10.2017 17:52

    Книжка «Цифровая схемотехника и архитектура компьютера » от Харрисов как вариант изучения данной темы не подходит?


  1. Jef239
    11.10.2017 18:17

    Похоже, что такт — это как скан в ПЛК. Но тогда непонятно, в чем проблемы в объяснениях? Программисты ПЛК легко понимают, что их программа крутится в цикле (ввод-обработка-вывод) и каждый цикл называется скан.


  1. RavOcean
    11.10.2017 22:03
    -2

    Ребят, то, что сейчас прочитаете может оказаться странным/глупым/непонятным, судите как хотите. Заметил, здесь сидят довольно таки умные, толковые люди, кто разбирается во многом по теме.
    У меня ситуация наоборот. Нет, не глупый, а лишь не понимаю многие посты здесь. В частности, по коду/разработке ПО.
    Всегда были две основне мечты: переехать в США и стать программистом(как звучит-то). Первое сделал. После долгих стараний сейчас нахожусь в округе Seattle(Bellevue, знаете по MS, Amazon, etc.), печатаю находясь в библиотеке колледжа среди разных студентов. Мне 22, родом из города Ташкент, Узбекистан. Но вот желание стать кодером ещё не исполнилось, к сожалению. Изредко бывает захожу на хабру, листаю посты, осознаю, мало что понимаю от них, но есть сильное желание знать, вникнуть. Знаю, есть тонны постов о том, как начать программировать, но хотелось бы услышать ваше мнение. Интерес у меня есть(не знаю, достаточный ли), желание есть, но вот где стартовая линия — не знаю. В колледже Я по «факультету» Software Development, но пока вожусь с начальной математикой, прочими социальными предметами. А хотелось бы начать себя готовить заранее.
    Поделитесь любым мнением, своим словом. Буду признателен каждому. Спасибо!


    1. sshmakov
      12.10.2017 11:17

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

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


  1. Space_Cowboy
    11.10.2017 22:03
    +3

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

    А тем кто хочет действительно разрабатывать цифровую логику (и автору в том числе) стоит начать с того чтобы примерить на себя роль инженера и ознакомиться хотябы с такой замечательной книжкой как:

    «ЦИФРОВАЯ СХЕМОТЕХНИКА И АРХИТЕКТУРА КОМПЬЮТЕРА»


  1. Sdima1357
    12.10.2017 09:00
    +2

    «Программисты CPU -они такие бестактные»