Помните, как в школе, решая примеры «в столбик», мы бормотали себе под нос: «один пишем, два в уме»? У процессора при вычислениях возникает похожая ситуация — где-то нужно хранить промежуточные результаты. Они не используются прямо сейчас, но вскоре обязательно пригодятся. В современных микропроцессорах работает множество различных блоков, обрабатывающих числа, — их называют исполнительными устройствами. Чтобы все они могли временно «складывать» свои «два в уме», требуется достаточно большой объем быстрой вспомогательной памяти.

Меня зовут Павел Кириченко, я автор курса «Схемотехника для начинающих» и старший инженер по разработке СнК в YADRO. В этом тексте мы разберемся, как устроены запоминающие ячейки внутри процессора, почему из них строят массивы памяти и какие задачи решают компиляторы — от оптимизации по скорости, площади и энергопотреблению до генерации файлов для САПР.

Почему появились компиляторы памяти

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

Если взять фотографию любого современного микропроцессора, то можно обнаружить на ней огромное количество похожих друг на друга прямоугольных блоков разных размеров, занимающих едва ли не большую часть площади. Это то, что разработчики чипов называют «памяти». Формальное же их название —  оперативные запоминающие устройства (ОЗУ).

Кристаллы CPU Zen 1 и Zen 2 под микроскопом
Кристаллы CPU Zen 1 и Zen 2 под микроскопом

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

Сразу уточню: термин «компилятор памяти» не имеет ничего общего с компиляторами, которые используются в программировании. Правильно будет считать их специфическим типом системы автоматизированного проектирования (САПР).

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

Как данные записываются в ячейку

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

Схема ячейки памяти, построенной на кольцевом соединении двух инверторов
Схема ячейки памяти, построенной на кольцевом соединении двух инверторов

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

Возможные состояния входа и соответствующие им состояния выхода
Возможные состояния входа и соответствующие им состояния выхода

Если соединить два инвертора в кольцо, каждый из них будет удерживать вход другого в устойчивом состоянии. Такое соединение образует положительную обратную связь. При включении питания на левом проводе B случайно может появиться любое из двух значений. Предположим, что это ноль. Через верхний инвертор I1 он превратится в единицу и окажется на правом проводе Bn. Нижний инвертор I2 преобразует эту единицу обратно в ноль и вернет его на левую сторону.

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

Запоминающая ячейка не должна хранить случайное значение — важно иметь возможность записывать в нее нужные данные. Для этого в схему добавляют два транзисторных ключа. Они управляются внешним сигналом WL и соединяют внутренние узлы ячейки с линиями BL и BLn.

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

Если подключить к внешним сторонам BL и BLn схему, которая задает нужные значения на входе запоминающей ячейки, например 0 слева и 1 справа, то при замыкании ключей и достаточной мощности эта схема сможет пересилить инверторы ячейки и изменить ее состояние. Если же в ячейке уже хранится то же самое значение, оно повторно подтвердится. Такая внешняя схема называется усилителем записи.

Зачем разделять чтение и запись

Внутри кольца запоминающей ячейки всегда устанавливается устойчивое состояние: на одном проводе хранится значение бита, а на другом — его противоположность. Поэтому один из них обозначают как прямой (B), а второй — как инверсный (Bn). Распределяют их обычно произвольно, так как схема симметрична. Главное — придерживаться выбранного обозначения на всех этапах разработки памяти, чтобы избежать путаницы при записи и чтении данных.

Через те же ключи к запоминающей ячейке можно подключить чувствительную схему, которая считывает данные, не изменяя их. Такая схема называется усилителем чтения. Усилитель записи и усилитель чтения работают через одну и ту же пару ключей, но поочередно. Когда требуется чтение, усилитель записи отключается и не подает сигналы на свои выходы, чтобы не мешать работе усилителя чтения. При таком варианте использования ячейки пару транзисторных ключей называют универсальным портом чтения-записи.

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

Схема двухпортовой запоминающей ячейки с отдельными линиями чтения и записи
Схема двухпортовой запоминающей ячейки с отдельными линиями чтения и записи

Мне доводилось самостоятельно проектировать и более сложные типы памяти — например, многопортовые, где было до 14 отдельных портов: 6 для записи и 8 для чтения. Я также занимался разработкой ассоциативных памятей, которые умеют искать данные внутри себя по заданному шаблону. Эти проекты не относились напрямую к созданию компиляторов памяти, но именно они в итоге принесли несколько патентов на оригинальные схемотехнические решения.

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

Площадь, мощность и скорость: в чем выигрывают ячейки памяти

Записывать и считывать один бит через запоминающую ячейку интересно, но неэффективно. Для этой задачи лучше подходит обычный D-триггер. Настоящие преимущества памяти проявляются тогда, когда из ячеек собирают большие массивы. Например, нам нужен блок на 2048 слов по 64 бита каждое. Перемножив эти числа, легко получить общее количество однобитных элементов, которые понадобятся. Так как каждый триггер состоит примерно из двух десятков транзисторов, то общее число этих «микроэлектронных атомов» в такой памяти оказалось бы огромным.

А вот однопортовая запоминающая ячейка содержит всего 6 транзисторов: по два в каждом инверторе и по одному в каждом ключе. Поэтому ее называют 6-транзисторной ячейкой. Двухпортовая ячейка устроена чуть сложнее и требует уже 8 транзисторов. Разница по сравнению с массивами на триггерах очевидна: память на ЗЯ занимает гораздо меньше площади. А рост площади всегда ведет к увеличению энергопотребления и снижению быстродействия.

Чтобы получить запоминающий массив для нашего примера, одинаковые ячейки памяти выстраивают в 2048 рядов по 64 ячейки в каждом. Такой ряд называют словом или регистром. Управляющие входы всех ключей в ряду соединены общей линией — ее называют словарной или регистровой шиной — wordline. Если на нее подается разрешающий сигнал, все ключи ячеек этого слова открываются. Если сигнал запрещающий — все ключи закрыты.

Как выглядят крайние угловые ячейки массива
Как выглядят крайние угловые ячейки массива

Выходы ячеек соединяются вертикально, образуя прямую и инверсную разрядные или битовые шины — bitline. По ним данные из ячеек поступают в усилители чтения или, наоборот, из усилителей записи — в ячейки. Но доступ открыт только у тех ячеек, чья словарная шина активирована. Такая матричная структура проста и удобна. ​​

Как выбрать нужный регистр

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

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

Как собрать сотни видов памяти в одно нажатие

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

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

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

Базовые расчеты для компилятора памяти

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

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

Поддержка режимов оптимизации

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

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

Выбор и моделирование опорных точек

После моделирования необходимо выполнить интерполяцию параметров, чтобы можно было предсказать характеристики любой конфигурации памяти. Например, пользователь может выбрать емкость от 16 до 4096 регистров с шагом 16 и ширину слова от 2 до 256 бит с шагом 1 (при этом оба параметра не могут быть максимальными). Компилятор должен выдать точные данные по рабочей частоте, энергопотреблению, площади и другим параметрам.

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

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

В любом компиляторе реализуется программный модуль, который генерирует все необходимые файлы-представления в стандартных для индустрии форматах: топологию, нетлист (текстовое описание схемы), файлы временны́х характеристик в разных условиях, файлы для расчета мощности и многое другое. Большинство файлов имеют текстовый формат и могут создаваться по шаблонам. Исключение — топология. Для нее приходится писать скрипты для САПР, чтобы автоматически расставлять элементы памяти в правильную структуру. При этом так исторически сложилось, что  языки скриптов внутри таких САПР не самые удобные и распространенные.

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

Проверка на практике: от схемы к кремнию

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

Любите разбираться в сложных схемах и находить элегантные инженерные решения? В YADRO открыта вакансия инженера-схемотехника. Вам предстоит работать с современными высокоскоростными интерфейсами и реальными серийными изделиями enterprise-класса.

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

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

Изображения сигналов внутри памяти, полученные в результате моделирования
Изображения сигналов внутри памяти, полученные в результате моделирования

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

Первое, что нужно проверить, — соответствие топологии технологическим нормам. Фабрика должна «уметь» изготавливать такую память. Для этого используются специальные файлы правил (Design Rule Check, DRC), которые предоставляются производителем технологии. Их загружают вместе с топологией в программу проверки. Она контролирует расстояния между проводами, размеры и расположение транзисторов и множество других параметров. Если нормы нарушены, фабрика откажется изготавливать чип.

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

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

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

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


  1. nerudo
    04.09.2025 11:22

    Расскажите, как проводится тестирование на соответствие параметрам? Ну те самые 1,17нс в даташите (условно).


    1. pgkirich Автор
      04.09.2025 11:22

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


  1. Dimonij2
    04.09.2025 11:22

    Как схемотехнически решается вопрос с записью в ячейку? Как так получается, что ничего не выгорает?


    1. pgkirich Автор
      04.09.2025 11:22

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


      1. Dimonij2
        04.09.2025 11:22

        То есть, фактически, сопротивление канала транзистора в инвертере работает как токоограничитель и выдерживает кз на время записи? То есть кз не совсем короткое?


        1. pgkirich Автор
          04.09.2025 11:22

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


  1. lgorSL
    04.09.2025 11:22

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

    А чем плохо, когда данные попадут сразу в несколько регистров? По-моему, это наоборот интересная возможность, например, можно одним махом занулить сразу несколько регистров. Разве так не делают? Из потенциальных проблем я вижу только то, что сигнал пойдёт в сразу несколько ячеек и будет нужен ток побольше.


  1. Mogwaika
    04.09.2025 11:22

    А есть фотки процессоров от ядра под микроскопом?


  1. DmitrMamov
    04.09.2025 11:22

    Как выбираете напряжение на bitline, при котором срабатывает усилитель считывания?

    Какими средствами получаете lib файл?


    1. pgkirich Автор
      04.09.2025 11:22

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

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