nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Мучаем 5576ХС4Т - часть 'h21 - преобразование двоичного кода в двоично-десятичный (BCD)

Часть 0 - покупаем, паяем, ставим драйвера и софт
Часть 1 - что это вообще за зверь?
Часть 2 - наша первая схема!
Часть 3 - кнопочки и лампочки
Часть 4 - делитель частоты
Часть 5 - подавление дребезга кнопки
Часть 6 - заканчиваем кнопочки и лампочки
Часть 7 - счетчики и жаба
Часть 8 - передатчик UART
Часть 9 - Hello, wolf!
Часть 'hA - приёмник UART
Часть 'hB - UART и жаба
Часть 'hC - полудуплексный UART.
Часть 'hD - МКО (МКИО, Mil-Std 1553) для бедных, введение.
Часть 'hE - приёмопередатчик МКО "из подручных материалов" (в процессе)
Часть 'hF - модуль передатчика МКО
Часть 'h10 - передатчик сообщений МКО
Часть 'h20 - работа с АЦП ADC124s051
Часть 'h21 - преобразование двоичного кода в двоично-десятичный (BCD)
Часть 'h22 - Bin2Bcd с последовательной выдачей данных
Часть 'h23 - перемножитель беззнаковых чисел с округлением
Часть 'h24 - перемножитель беззнаковых чисел, реализация
Часть 'h25 - передаём показания АЦП на компьютер
Часть 'h26 - работа над ошибками (быстрый UART)
Часть 'h27 - PNG и коды коррекции ошибок CRC32
Часть 'h28 - передатчик изображения PNG
Часть 'h29 - принимаем с ПЛИС изображение PNG
Часть 'h2A - ZLIB и коды коррекции ошибок Adler32
Часть 'h2B - ускоряем Adler32
Часть 'h2C - формирователь потока Zlib
Часть 'h2D - передаём сгенерированное PNG-изображение
Часть 'h2E - делим отрезок на равные части
Часть 'h2F - знаковые умножители, тысячи их!
Часть 'h30 - вычислитель множества Мандельброта
Часть 'h31 - ускоренные сумматоры
Часть 'h32 - ускоренные счётчики (делаем часы)
Часть 'h33 - ускоряем ВСЁ
Часть 'h34 - ускоренные перемножители
Часть 'h35 - умножители совсем просто
Часть 'h36 - уравновешенный четверичный умножитель


Мы научились получать 12 бит с АЦП, и теперь хочется отобразить данное значение в "понятной человеку" форме - может быть, на 7-сегментном индикаторе, а может - отправить по UART на компьютер, в простой символьной форме, чтобы не пришлось "на ровном месте" городить ещё и программу на компе для расшифровки данных, идущих с ПЛИС, и искать потом, где ошибка - здесь или там?



Драндулет, который нам нужен, называется binary to BCD converter - преобразователь двоичного кода в двоично-десятичный. Казалось бы, вбиваешь в "гугле" заветную фразу, добавляешь слово Verilog - и выбирай готовые решения. Но у них всех есть фатальный недостаток!

Так, мне хотелось применить 16-битный преобразователь - хорошее, красивое число, удобно в памяти хранить. Набрёл на этот красивый код - ну действительно, всего 14 строчек кода, и можно задавать любую разрядность! Для 16 бит эта штука скомпилилась в 192 логических элемента - это чисто комбинаторная схема, и задержка распространения сигнала составляет 60,8 нс - это почти 5 тактовых импульсов при частоте 80 МГц! То есть, если мы используем её в своих "синхронных" разработках, то обязаны ещё добавить счётчик до 5, чтобы только по прошествии этих импульсов мы занесли значение куда следует, причём входы всё это время должны быть зафиксированы - для этого надо бы поставить ещё 16 "защёлок".

[Для меньшей разрядности всё чуточку лучше]Для меньшей разрядности всё чуточку лучше.
8-битный преобразователь задействует 33 логических элемента, а задержка распространения - 23,5 нс, т.е менее 2 тактовых импульсов.
12-битный использует 90 ЛЭ, задержка 35,7 нс (менее 3 тактовых импульсов)
а вот 32-битный раздувается до 890 ЛЭ и задержки в 155 нс (менее 13 тактовых импульсов). Ставишь таких 10 штук - и ПЛИС внезапно заканчивается :)


Ладно, поищем итеративный вариант - он изначально синхронный - по стартовому импульсу он защёлкнет входные данные, и даст понять, когда у него появились корректные выходные данные. И за счёт "разнесения во времени, а не в пространстве", такой преобразователь должен быть куда компактнее. Скачиваем вот эту штуку, там и вовсе реализуется "два вложенных цикла", то есть как будто бы количество "железа" должно быть сведено к минимуму. Но нет, он синтезируется в 171 логический элемент, а время выполнения составляет 2,28 мкс (при тактовой частоте 80 МГц), то есть занимает 185 тактов!

Вот это уже неплохой вариант - синтезируется в 94 логических элемента и выполняется за 17 тактов.

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

Результаты такие: мой "функциональный аналог" рассмотренных схем занимает 45 логических элементов (ЛЭ, LE) и выполняет работу за 15 тактов. Вариант с последовательной выдачей десятичных разрядов уже "потолще" - 67 ЛЭ (на данный момент), но кажется, что он всё-таки компактнее, чем первый вариант + сдвиговый регистр с управляющей логикой.

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

Метод упоминался недавно на канале computerphile: https://www.youtube.com/watch?v=eXIfZ1yKFlA

Допустим, мы хотим преобразовать 8-битное число в двоично-десятичную форму. Например, 1000 00002 = 0x80 должно превратиться в 0001 0010 10002 = 0x128. Теперь мы можем подать каждые 4 бита на соответствующий 7-сегментный индикатор, а можем спереди добавить 00112 - и получившийся байт станет ASCII-представлением нужной нам цифры. Например, 0011 00012 = 0x31 - это код цифры "1", а 0011 10002 = 0x38 - код цифры 8.

"Канонический" алгоритм выглядит так: мы записываем в одну строку (в один регистр) сначала 12 бит результата, которые в начале работы будут нулевыми, а затем, справа от них - исходные 8 бит. Для удобства разделим их вертикальной чертой, а также поставим пробел между каждыми 4 битами:
0000 0000 0000 | 1000 0000


На каждом шаге алгоритма мы проверяем каждый "десятичный разряд" - не стал ли он больше, чем 4? Если не стал - не трогаем его, в противном случае прибавляем к нему 3. И после этого сдвигаем всю строку влево на один бит.

После первого шага получим следующее (пока все разряды были нулевыми, поэтому достаточно только сдвинуть):
0000 0000 0001 | 0000 000

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

Ещё один шаг:
0000 0000 0010 | 0000 00

Опять всё просто: мы просто осуществили сдвиг.

Ещё шаг:
0000 0000 0100 | 0000 0

Младший разряд достиг значения 4 - по-прежнему коррекция не требуется, выполняем только сдвиг:

0000 0000 1000 | 0000

А вот теперь младший десятичный разряд принял значение 8. Это значит, что мы должны прибавить туда тройку:
0000 0000 1011 | 0000

и только после этого сдвинуть:

0000 0001 0110 | 000

Если смотреть на десятичные значения, которые у нас идут в процессе работы, мы видим:
000
001
002
004
008
016

то есть, по мере "пропихивания" того двоичного значения, что сидело справа, мы следим, чтобы здесь уже принимались "правила десятичной арифметики", и 8 * 2 = 16, а не 10.

Поехали дальше. В младшем разряде у нас шестерка, поэтому прибавляем 3:
0000 0001 1001 | 000


Теперь сдвигаем влево:
0000 0011 0010 | 00

(мы получили десятичную запись 032)

Все три числа не превышают 4, так что снова делаем сдвиг:

0000 0110 0100 | 0

(десятичная запись 064)

Коректируем разряд десятков, где у нас появилась шестёрка (больше четырёх), т.е прибавляем 3:

0000 1001 0100 | 0


И наконец, сдвигаем:
0001 0010 1000


Больше у нас справа не осталось битов, мы "пропихнули" все, а значит, преобразование завершено, и мы получили ответ: 0x128 - всё верно.

Метод вполне подходящий для ПЛИС: сдвиговые регистры - это наш хлеб, ну а когда надо не просто осуществить сдвиг, а ещё выполнить коррекцию - это довольно легко выполняется комбинаторной логикой.

Самую компактную (но неоконченную) реализацию предлагают в Xilinx Application Note 029:



Мы составляем преобразователь произвольной разрядности из однотипных элементов, каждый отвечает за один десятичный разряд. В начале преобразования мы их всех обнуляем, подавая сигнал Init, а затем начинаем каждый такт подавать на крайний правый элемент очередной бит данных (от старшего к младшему), они распространяются по цепочке, и когда все биты "закончатся", у нас сформируется правильный двоично-десятичный код.

Сразу видно, что у нас неплохая масштабируемость - от увеличения разрядности количество "железа" увеличивается пропорционально, и во столько же раз увеличивается количество тактов, необходимых для завершения преобразования.

Вот как предлагается реализовывать каждый модуль:


Сразу видно: наши люди! На самом деле, логика довольно простая. Если пришёл сигнал Init, мы просто всё обнуляем. В противном случае, из 4 исходных бит мы формируем скорректированное значение: числа от 0 до 4 останутся без изменений, к числам от 5 до 9 мы прибавим тройку, а с числами от 10 до 15 может случиться что угодно, нам без разницы, поскольку они всё равно появиться не могут!

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

Вот как можно написать этот модуль на верилоге, "доверившись компилятору":
module BinaryToBcdAtomicBlock (input clk, input sclr, input bIn, output reg [3:0] Q, output bOut);

	assign bOut = (Q > 4); //that's senior digit (carry-out)
	
	wire [3:0] Qmod = bOut? Q + 2'b11 : Q;

	always @(posedge clk)
		Q <= sclr? 1'b0 : {Qmod[2:0], bIn}; 
endmodule


сам по себе он "компилируется" в 6 ЛЭ - и это неплохой результат. Кстати, мы немножко "покоцали" код из XAPP - там даже bOut (который передаёт бит по цепочке) обнуляется по Init, но это на самом деле не требуется - ведь когда подан сигнал Init, последующий каскад всё равно проигнорирует bIn и всё обнулит!

Теперь сделаем весь преобразователь целиком. Вот его заголовок:
module XilinxBinaryToBcd (input clk,
                          input [15:0] D,
                          input start,
                          output [19:0] Q,
                          output finished);


clk - таковая частота,
D - двоичное число, которое хотим преобразовать,
start - импульс длительностью в один период clk, по нему мы "защёлкиваем" входные данные и начинаем работать.
Q - выходные двоично-десятичные данные.
finished - здесь появится импульс длительностью в один период clk, когда на выходе будут правильные данные - тут-то мы и должны их "изловить", поскольку уже на следующем такте они исчезнут (исказятся). Это - цена простоты. В дальнейшем мы сделаем модуль с более "приятными" характеристиками.

И начинаем, как всегда, с "командоаппарата". Кажется, что нам вполне хватит для счастья 16 состояний:

wire [3:0] state; //as usual, 0000 is Idle, 1111 is last step
wire IsIdle = (state == 0); //no shortcuts probably, all states are used!
wire TC; //Terminal Count
    
lpm_counter	counter (
				.clock (clk),
				.cnt_en (~IsIdle | start),
				.q (state),
				.cout (TC) );
	defparam
		counter.lpm_direction = "UP",
		counter.lpm_port_updown = "PORT_UNUSED",
		counter.lpm_type = "LPM_COUNTER",
		counter.lpm_width = 4;

assign finished = TC; 


Как всегда: в нулевом состоянии он "стоит на месте", но по приходу импульса start наконец-то приходит в движение, досчитывает до 15, и в этот момент на проводе TC (Terminal Count) появляется единичка, которую мы транслируем на выход finished.

Теперь разбираемся со входным сдвиговым регистром:
reg [15:0] SR = 1'b0; //shift register
always @(posedge clk)
	SR <= start? D : SR << 1;

Святая простота! Никаких входов разрешения - или сдвигаем, или заносим новое значение, вот и всё!

И наконец, ставим "батарею" из наших преобразователей:
wire B0toB1;
BinaryToBcdAtomicBlock Block0 (
	.clk (clk),
	.sclr (start),
	.bIn (SR[15]),
    .Q (Q[3:0]),
    .bOut (B0toB1));
    
wire B1toB2;
BinaryToBcdAtomicBlock Block1 (
	.clk (clk),
	.sclr (start),
	.bIn (B0toB1),
    .Q (Q[7:4]),
    .bOut (B1toB2));

wire B2toB3;
BinaryToBcdAtomicBlock Block2 (
	.clk (clk),
	.sclr (start),
	.bIn (B1toB2),
    .Q (Q[11:8]),
    .bOut (B2toB3));
    
wire B3toB4;
BinaryToBcdAtomicBlock Block3 (
	.clk (clk),
	.sclr (start),
	.bIn (B2toB3),
    .Q (Q[15:12]),
    .bOut (B3toB4));
  
wire B4toB5;
BinaryToBcdAtomicBlock Block4 (
	.clk (clk),
	.sclr (start),
	.bIn (B3toB4),
	.Q (Q[19:16]),
	.bOut());


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


То есть, мы, так получается, сосчитали до 15 (счёт начался с единицы, а не с нуля, как обычно), а в реальности нужно 17 тактов, поскольку на первом такте (после прихода импульса start) мы только-только "защёлкнули" входные данные, и лишь к следующему такту сдвинули их хоть раз!

Можно поставить небольшой костыль: вместо строчки
assign finished = TC;


напишем:
reg AlmostFinished = 1'b0;
reg ReallyFinished = 1'b0;
assign finished = ReallyFinished;
always @(posedge clk) begin
	AlmostFinished <= TC;
	ReallyFinished <= AlmostFinished;
end


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

Вот, наконец, код модуля целиком:
module XilinxBinaryToBcd (input clk, input [15:0] D, input start, output [19:0] Q, output finished);

wire [3:0] state; //as usual, 0000 is Idle, 1111 is last step
wire IsIdle = (state == 0); //no shortcuts probably, all states are used!
wire TC; //Terminal Count
    
lpm_counter	counter (
				.clock (clk),
				.cnt_en (~IsIdle | start),
				.q (state),
				.cout (TC) );
	defparam
		counter.lpm_direction = "UP",
		counter.lpm_port_updown = "PORT_UNUSED",
		counter.lpm_type = "LPM_COUNTER",
		counter.lpm_width = 4;


reg AlmostFinished = 1'b0;
reg ReallyFinished = 1'b0;
assign finished = ReallyFinished;
always @(posedge clk) begin
	AlmostFinished <= TC;
	ReallyFinished <= AlmostFinished;
end

reg [15:0] SR = 1'b0; //shift register
always @(posedge clk)
	SR <= start? D : SR << 1;

wire B0toB1;
BinaryToBcdAtomicBlock Block0 (
	.clk (clk),
	.sclr (start),
	.bIn (SR[15]),
    .Q (Q[3:0]),
    .bOut (B0toB1));
    
wire B1toB2;
BinaryToBcdAtomicBlock Block1 (
	.clk (clk),
	.sclr (start),
	.bIn (B0toB1),
    .Q (Q[7:4]),
    .bOut (B1toB2));

wire B2toB3;
BinaryToBcdAtomicBlock Block2 (
	.clk (clk),
	.sclr (start),
	.bIn (B1toB2),
    .Q (Q[11:8]),
    .bOut (B2toB3));
    
wire B3toB4;
BinaryToBcdAtomicBlock Block3 (
	.clk (clk),
	.sclr (start),
	.bIn (B2toB3),
    .Q (Q[15:12]),
    .bOut (B3toB4));
  
BinaryToBcdAtomicBlock Block4 (
	.clk (clk),
	.sclr (start),
	.bIn (B3toB4),
	.Q (Q[19:16]),
	.bOut());
endmodule


Он компилируется в 55 логических элементов - не так уж и плохо.

Как показал опыт, попытка упростить логику внутри каждого блока не приводит к успеху, количество потребных ЛЭ не уменьшается.

Одна "хитрость" - нам вовсе не обязательно задействовать полноценный блок для старшего разряда - мы знаем, что диапазон чисел, представимых 16 битами - от 0 до 65535, поэтому нам и последний, 20-й бит не нужен (он всегда нулевой), да и беспокоиться о коррекции не стоит - коррекция производится, когда мы осуществляем перенос на старший разряд, а тут переноса заведомо не будет! Поэтому уменьшаем ширину шины до 19 бит, удаляем один блок, а вместо него пишем самый обычный сдвиговый регистр:
reg [2:0] senior = 1'b0; //[18:16]
assign Q[18:16] = senior;

always @(posedge clk)
	senior <= {senior[1:0], B3toB4};


заметим, что его мы даже не обнуляем - нет нужды, ведь всё старое содержимое в любом случае вытеснится к концу работы! (модули с коррекцией мы ОБЯЗАНЫ обнулять, поскольку случайно оставшееся там значение может никуда не пропасть. Скажем, 1 превратится в 2, потом в 4, в 8, в 6, и снова в 2 - и так далее. Только ноль и пятёрка "уйдут")

Данные манёвры экономят нам 3 ЛЭ: теперь требуется 52 логических элемента.

Следующая идея: а не зациклить ли нам эту штуку? Так и быть, оставим шину в 20 бит (чтобы не мучаться потом при мультиплексировании и пр.), но в старший десятичный разряд при старте будем запихивать 4 младших бита двоичного числа. Тогда сам сдвиговый регистр можно будет сократить на те же самые 4 бита.
Теперь описание сдвигового регистра и регистра для старшего разряда выглядят так:
reg [11:0] SR = 1'b0; //shift register

reg [3:0] senior = 1'b0; //[18:16]
assign Q[19:16] = senior;

always @(posedge clk) begin
	senior <= start? D[3:0] : {senior[2:0], B3toB4};
	SR     <= start? D[15:4]: {SR[10:0], senior[3]};
end


Кроме того, в блоке, отвечающем за самый младший разряд, теперь вместо SR[15] надо подать на вход SR[11] (к счастью, в противном случае компилятор грязно выругается, так что забыть не получится!)

В плане поведения ничего не поменялось, а число элементов снизилось - теперь нам хватает 49 штук!

И наконец, ещё один маленький "штрих". На самом деле, нам вовсе не обязательно начинать с такого состояния:
0000 0000 0000 0000 0000 | D[15:0]


(скорее, с такого:
D[3:0] 0000 0000 0000 0000 | D[15:4]
и осуществлением циклического сдвига вместо линейного).

Ведь первые три сдвига влево точно не потребуют коррекции. Мы возмущались, что импульс finished приходил на 2 такта раньше - так загрузим младшие 2 бита СРАЗУ ЖЕ! Это позволит ещё ужать сдвиговый регистр, а также выкинуть наш "костыль". То есть, предлагается начать с такого состояния:
D[3:0] 0000 0000 0000 00 D[15:14] | D[13:4]
.

Придётся для этого создать ещё один модуль:
module BinaryToBcdLeastSignBlock (input clk, input sclr, input [1:0] D, input bIn, output reg [3:0] Q, output bOut);

	assign bOut = (Q > 4); //that's senior digit (carry-out)
	
	wire [3:0] Qmod = bOut? Q + 2'b11 : Q;

	always @(posedge clk)
		Q <= sclr? {2'b00, D} : {Qmod[2:0], bIn}; 
endmodule

он вместо полного обнуления заносит в младшие биты заданное значение - кусочек исходного бинарного числа.

Наконец, приведём полный код нашей наиболее укуренной версии этого преобразователя:
module XilinxBinaryToBcd (input clk, input [15:0] D, input start, output [19:0] Q, output finished);

wire [3:0] state; //as usual, 0000 is Idle, 1111 is last step

wire IsIdle = (state == 0);
wire TC; //Terminal Count
    
lpm_counter	counter (
				.clock (clk),
				.cnt_en (~IsIdle | start),
				.q (state),
				.cout (TC) );
	defparam
		counter.lpm_direction = "UP",
		counter.lpm_port_updown = "PORT_UNUSED",
		counter.lpm_type = "LPM_COUNTER",
		counter.lpm_width = 4;

assign finished = TC;

wire B0toB1;
BinaryToBcdLeastSignBlock Block0 (
	.clk (clk),
	.sclr (start),
	.D (D[15:14]),
	.bIn (SR[9]),
    .Q (Q[3:0]),
    .bOut (B0toB1));

wire B1toB2;
BinaryToBcdAtomicBlock Block1 (
	.clk (clk),
	.sclr (start),
	.bIn (B0toB1),
    .Q (Q[7:4]),
    .bOut (B1toB2));

wire B2toB3;
BinaryToBcdAtomicBlock Block2 (
	.clk (clk),
	.sclr (start),
	.bIn (B1toB2),
    .Q (Q[11:8]),
    .bOut (B2toB3));
    
wire B3toB4;
BinaryToBcdAtomicBlock Block3 (
	.clk (clk),
	.sclr (start),
	.bIn (B2toB3),
    .Q (Q[15:12]),
    .bOut (B3toB4));

reg [9:0] SR = 1'b0; //shift register

reg [3:0] senior = 1'b0; //[18:16]
assign Q[19:16] = senior;

always @(posedge clk) begin
	senior <= start? D[3:0] : {senior[2:0], B3toB4};
	SR     <= start? D[13:4]: {SR[8:0], senior[3]};
end
endmodule

module BinaryToBcdAtomicBlock (input clk, input sclr, input bIn, output reg [3:0] Q, output bOut);

	assign bOut = (Q > 4); //that's senior digit (carry-out)
	wire [3:0] Qmod = bOut? Q + 2'b11 : Q;

	always @(posedge clk)
		Q <= sclr? 1'b0 : {Qmod[2:0], bIn}; 
endmodule

module BinaryToBcdLeastSignBlock (input clk, input sclr, input [1:0] D, input bIn, output reg [3:0] Q, output bOut);

	assign bOut = (Q > 4); //that's senior digit (carry-out)
	wire [3:0] Qmod = bOut? Q + 2'b11 : Q;

	always @(posedge clk)
		Q <= sclr? {2'b00, D[1:0]} : {Qmod[2:0], bIn}; 
endmodule



Эта штуковина синтезируется в 45 логических элементов. Если бы мы ужались ещё на один бит, то пришлось бы делать логику параллельной загрузки значения 0010 в счётчик, чтобы сигнал окончания пришёл вовремя. Кроме того, понадобился бы один дополнительный ГФ (LUT) для формирования значения 2-го бита младшего разряда, и мы бы пришли к 46 ЛЭ, поэтому я и не стал такой вариант предлагать.

Не знаю, можно ли сделать этот модуль ещё компактнее - у меня пока не получилось. Заметим, что нам удалось его ещё и "ускорить" хоть самую малость - вместо 17 тактов он тратит 15 - мелочь, а приятно! Результаты моделирования работы приведены в начале поста.


О преобразователе с последовательной выдачей десятичных разрядов - в следующей части.
Tags: ПЛИС, математика, работа, странные девайсы
Subscribe

Recent Posts from This Journal

  • Так ли страшно 0,99969 вместо 1?

    В размышлениях о DMA, о возможных последствиях "смешивания" старых и новых значений при выдаче целевой информации, опять выполз вопрос: насколько…

  • Как продлить агонию велотрансмиссии на 1500+ км

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

  • DMA для QuatCore

    Вот фрагмент схемы нашего "процессорного ядра" QuatCore: Справа сверху "притаилась" оперативная память. На той ПЛИС, что у меня есть сейчас…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments