Один способ вычисления логарифма по основанию 2

Вычисление логарифмов довольно распространённая операция в цифровой обработке сигналов. Чаще пожалуй приходится считать только свёртки (умножение с накоплением) и амплитуды с фазами. Как правило для вычисления логарифмов на FPGA применяется алгоритм CORDIC в гиперболическом варианте, требующий только таблицы и простых арифметических операций. Однако это не всегда бывает удобно, особенно если проект большой, кристалл маленький и начинаются танцы с оптимизацией. Именно с такой ситуацией и пришлось мне однажды столкнуться. Оба порта блока RAM (Cyclone IV) уже плотненько были в работе, не оставляя свободных окон. Использовать ещё один блок под гиперболический CORDIC не хотелось. Зато был умножитель, для которого во временной диаграмме получалось приличное свободное окно. Денёк подумав, я сочинил следующий алгоритм, в котором не используется таблиц, но есть умножение, точнее возведение в квадрат. И поскольку схемотехнически возведение в квадрат проще общего случая умножения, возможно этот алгоритм представляет интерес для специализированных микросхем, хотя для FPGA разницы конечно нет. Подробнее под катом.

Объяснять что к чему, тут проще для действительных чисел. С них и начнём. К целочисленной реализации перейдём потом.

Пусть есть число X. Требуется найти число Y такое, чтобы $X=2^Y$.
Положим так же что X лежит в интервале от 1 до 2. Это не слишком ограничивает общность, поскольку X всегда можно перевести в этот интервал умножением или делением на степень двойки. Для Y это будет означать добавление или вычитание целого числа, что делается легко. Итак X лежит в интервале от 1 до 2. Тогда Y будет лежать в интервале от 0 до 1. Запишем Y как бесконечную двоичную дробь:

$Y={b_0}2^0 + {b_1}2^{-1}+...+{b_n}2^{-n}+...$


Коэффициенты ${b_i}$ в этой записи есть ни что иное, как просто биты двоичного представления числа Y. Причём поскольку Y меньше 1, очевидно что ${b_0}$=0.

Возведём наше первое уравнение в квадрат: $X^2=2^{2Y}$ и как и ранее, запишем двоичное представление числа 2Y. Очевидно, что

$2Y={b_1}2^0+{b_2}2^{-1}+...+{b_n}2^{-(n-1)}+...$

Т.е. биты ${b_i}$ остались теми же, просто сдвинулись степени двойки. Бита ${b_0}$ в представлении нет, потому что он равен нулю.

Возможны два случая:

1) $X^2 > 2$, 2Y > 1, ${b_1} = 1$

2) $X^2 < 2$, 2Y < 1, ${b_1} = 0$

В первом случае в качестве нового значения X примем $X^2/2$, во втором — $X^2$.

В итоге задача свелась к прежней. Новое X опять лежит в интервале от 1 до 2, новое Y от 0 до 1. Но мы узнали один бит результата. Делая такие же шаги в дальнейшем, мы можем получить сколько угодно битов Y.

Посмотрим как это работает в программе на С:

#include <stdio.h>
#include <math.h>
int main()
{
	double w=1.4;
	double s=0.0;
	double a=0.5;
	double u=w;
	for(int i=0; i<16; i++)
	{
		u=u*u;
		if(u>2)
		{
			u=u/2;
			s+=a;
		}
		a*=0.5;
	}
	w=log2(w);
	double err=100*abs(2*(s-w)/(s+w));
	printf("res=%f, log=%f, err=%f%c\n",s,w,err,'%');
	return 0;
}

Мы посчитали логарифм с точностью до 16 бит и сравнили с тем, что даёт математическая библиотека. Программа вывела:

res=0.485413, log=0.485427, err=0.002931%

Результат совпал с библиотекой с точностью 0.003%, что показывает работоспособность нашего алгоритма.

Перейдём к целочисленной реализации.

Пусть N-разрядные двоичные беззнаковые числа, отображают интервал [0, 1]. Для удобства будем считать единицей число $2^N$, а не $2^N-1$, и соответственно двойкой число $2^{N+1}$. Напишем программу по образу и подобию предыдущей, но работающую с целыми числами:


#include <stdio.h>
#include <math.h>
#define DIG 	18            //разрядность чисел
#define N_BITS  16            //число бит которое считаем
unsigned ONE=1<<(DIG-1);      //единица
unsigned TWO=ONE<<1;          //двойка
unsigned SCALE=1<<(N_BITS+1);   //масштаб логарифмов
unsigned myLog(unsigned w)
{
	unsigned s=0;
	unsigned long long u=w;
	for(int i=0; i<N_BITS+1; i++)
	{
		s<<=1;
		u=(u*u)>>(DIG-1);  //берём только старшие разряды !
		if(u&TWO)   //тут достаточно одного бита для сравнения
		{
			u>>=1;
			s+=1;
		}
		printf("%X\n", (int)u);
	}
	return s;
}
int main()
{
	double w=1.2345678;
	unsigned iw=(unsigned)(ONE*w);
	double dlog=log2(w);
	unsigned ilog=myLog(iw);
	unsigned test=(unsigned)(SCALE*dlog);
	int err=abs((int)(ilog-test));
	printf("val=0x%X, res=0x%X, log=0x%X, err=%d\n",iw,ilog,test,err);
	return 0;
}

Поиграв в программе с разными разрядностью чисел (DIG), точностью вычислений (N_BITS), и аргументами логарифма (w), видим, что всё вычисляется правильно. В частности с теми параметрами, которые заданны в этом исходнике, программа выдаёт:

val=0x27819, res=0x9BA5, log=0x9BA6, err=1

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

module logarithm(
		input                 clk,      //клок
		input                 wr,       //сигнал записи данных
		input[17:0]           din,      //входная шина данных
		output[nbits-1:0]     dout,     //выходная шина данных
		output                rdy       //сигнал готовности		
		);
	parameter nbits=16;       //точность вычисления   

Входная шина принята 18-разрядной, соответственно разрядности умножителей в Cyclone IV. Числа на наш модуль должны поступать нормализованные. Т.е. со старшим битом, равным единице. В моём проекте это выполнялось автоматически. Но в случае чего реализовать нормализатор, думаю никому труда не составит. Точность вычислений задаётся параметром nbits, по умолчанию равным 16. Модуль считает по одному биту за такт, и за 16 тактов вычислит логарифм с точностью до 16 битов. Если надо быстрее с той же точностью или точнее с той же скоростью, надеюсь никому не составит особого труда разделить модуль на несколько устройств и конвейеризовать.

Вот полный код модуля и теста
//--------------------- logarithm.v ------------------------------//
module logarithm(
		input                 clk,      //клок
		input                 wr,       //сигнал записи данных
		input[17:0]           din,      //входная шина данных
		output[nbits-1:0]     dout,     //выходная шина данных
		output                rdy       //сигнал готовности		
		);
	parameter nbits=16;       //точность вычисления   
	
	reg[4:0]  cnt;      //счетчик
	reg[17:0] acc;      //рабочий регистр-аккумулятор
	reg[nbits-1:0] res; //результат
	
	always @(posedge clk)
		if(wr) cnt<=nbits+1;
		else
		if(cnt != 0) 
			cnt<=cnt-1;
		
	wire[35:0] square=acc*acc;  //квадрат аккумулятора
	wire bit=square[35];        //бит результата
	wire[17:0] next = bit ? square[35:18] : square[34:17]; //новый аккумулятор 
	
	always @(posedge clk)
		if(wr) acc<=din;
		else
		if(cnt != 0) begin
			acc<=next;
			#10 $display("%X", acc);
		end
		
	always @(posedge clk)
		if(wr) res<=0;
		else
		if(cnt != 0) begin
				res[nbits-1:1]<=res[nbits-2:0];
				res[0]<=bit;
			end
		
	assign dout=res;
	assign rdy=(cnt==0);
endmodule

//======================== testbench.v =====================//
module testbench();
	
	reg clk;      //клок
	always 
		#100 clk=~clk;
		
	reg wr;            //запись
	reg[17:0] din;     //вход
	wire rdy;          //готовность
	wire[15:0] dout;   //выход 
		
	logarithm log2(
			.clk    (clk),
			.wr     (wr),
			.din    (din),
			.dout   (dout),
			.rdy    (rdy) 
			);
	
	// ждёт n клоков и немного ещё
	task skipClk(integer n); 
		integer i;
		begin
			for(i=0; i<n; i=i+1)
				@(posedge clk);
		#10 ;
		end
	endtask	
	
	initial
		begin   //тест
			$dumpfile("testbench.vcd");
			$dumpvars(0, testbench);
			clk=0;
			wr=0;
			din=18'h27819;
			skipClk(3);
			wr=1;
			skipClk(1);
			wr=0;
			@(rdy);
			skipClk(3);
			$display("value=%X, result=%X", din, dout);
			$display("Done !");
			$finish;
		end
endmodule


Запустим тест вот таким скриптом:

#!/bin/sh
rm -f *.vvp
rm -f *.vcd
iverilog -o testbench.vvp logarithm.v testbench.v
vvp testbench.vvp
gtkwave testbench.vcd testbench.gtkw 

Запустив тест, видим окончательный вывод симулятора — value=27819, result=9ba5. Верилог выдал то же самое что С. Временная диаграмма тут довольно тривиальна и особого интереса не представляет. Поэтому её не привожу.

Сравним промежуточный вывод симулятора (acc) и программы на С (s):
Verilog C
30c5d 30C5D
252b1 252B1
2b2bc 2B2BC
3a3dc 3A3DC
35002 35002
2be43 2BE43
3c339 3C339
38a0d 38A0D
321b0 321B0
273a3 273A3
30163 30163
24214 24214
28caf 28CAF
34005 34005
2a408 2A408
37c9d 37C9D
30a15 30A15


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

На этом пожалуй всё. Надеюсь кому-то этот мой опыт окажется полезен.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 37

    +1
    К сожалению в понедельник 30.09.2019 уезжаю на недельку в края, где тырнетов нету, а расскажи местным про такое чудо — в жизни не поверят. Поэтому на комментарии буду отвечать где-то до 16:00 понедельника. Потом уже только через неделю.
      0
      Спасибо за статью.

      P.S. Не удержался:
      уезжаю на недельку в края, где тырнетов нету

      На недельку, до log222
      Я уеду в Комарово
        0
        Я больше вот это предпочитаю
        www.youtube.com/watch?v=SI4eqy1ZvTc
        Хотя места у меня конечно куда более глухие, чем Ленинград :))
      0
      Хороший ход.
      Покайфовал.
        +1
        Спасибо! Первый раз тут публикуюсь, опасался что заминусуют :))
          0

          Откуда такие предрассудки?))

            +1
            Думаю, из личных наблюдений, как легко могут слить карму за комментарии.
              0
              Да начитался тут вчера обсуждений :))
                0

                "не читайте до обеда советских газет" и продолжайте писать хорошие статьи.

          +3

          "Всё открыто до нас" ;) https://en.wikipedia.org/wiki/Binary_logarithm

            +1
            Я и не сомневался, что это известно(ну не могло быть неизвестно !), но додумываться пришлось самому. По Вашей ссылке бегло посмотрел, вроде бы речь идёт о чём-то похожем. Но сходу увидеть в точности я этого не могу. Возможно тот же алгоритм, возможно другой. Надо подробнее почитать.
            0

            Я не в теме, но не является ли это реинкарнацией Кордика?

              0
              Совсем нет. Кордик требует таблицы. Вот гляньте ru.wikipedia.org/wiki/CORDIC. Если в теории по ссылке заменить тригонометрические функции гиперболическими, вместо обычного арктангенса будет считаться гиперболический, который и есть логарифм. А тут была цель как раз всё сделать без таблицы. Но поскольку бесплатный сыр бывает только в мышеловке, за это пришлось заплатить умножителем. В том проекте меня такой размен устраивал. Всё равно умножитель там использовался и имел приличное свободное окно. Я его просто дополнительно нагрузил, сэкономив при этом блочную RAM.
              +2

              А я такие вещи делал на ПЛИС методом Ньютона-Рафсона. Там тоже нужен один умножитель. Интересно дает ли он лучший результат за то же число тактов?

                0
                Трудно сказать. Сходимость Ньютона-Рафсона определяется производной. А значит скорее всего меняется от итерации к итерации. У меня тут стабильно и честно один бит на итерацию. С Ньютоном-Рафсоном как-то не догадался. Хотя казалось бы чего проще… Надо будет попробовать. Любопытный вопрос!
                0
                А можно раскрыть в форме гайда / туториала тему верификации такого кода на Verilog и синтеза его под физическую FPGA? Какой софт вы для всего этого используете?
                  +1
                  Ну как бы не претендую… Вобщем тут немного раскрыта моя манера разработки. Отталкиваясь от чего-то тёплого, лампового и аналогового, сначала перейти к жесткой фиксированной точке, а после к совсем уже жестокому железу. Переход к верилогу от целочисленной (с фиксированной точкой) модели на С как правило достаточно безболезненный. Ибо уже на С становятся достаточно хорошо видны некоторые будущие аппаратные компоненты (счетчики, сдвиговые регистры, аккумуляторы, мультиплексоры и т.п.). Ну и последний этап моделирования — совместная доводка С и верилога до стадии, когда их результаты совпадают бит в бит. Для моделирования использую icarus verilog как симулятор и gtkwave как просмотрщик временных диаграмм. Под Windows это тоже есть. Для громадных тестов — verilator.

                  Если говорить о реальном железе, это синтезировалось разумеется на Квартусе, чем же ещё Cyclone IV разводить? Ну а если Вы новичок и хотите освоить FPGA, очень советую начать с платки iCE40-HX8K Breakout Board www.latticesemi.com/en/Products/DevelopmentBoardsAndKits/iCE40HX8KBreakoutBoard.aspx. FPGA там не очень большая, всего 8К ячеек. Но во-первых этого вполне достаточно чтобы собрать даже 32-битный процессор для которого написан компилятор С. Во-вторых с ней крайне удобно работать. Там стоит двухканальный чип FTDI. По одному каналу шьётся FPGA. А другой канал работает как UART, выведен на ноги FPGA и виден из проекта. Если Вы реализуете UART в своём проекте (на этом хабе была статья про его реализацию), то сможете общаться с FPGA прямо с компьютера, причем через тот же usb-кабель, через который шили проект. Дико удобно! В третьих софт. В отличии от не к ночи помянутых Квартуса и Вивадо с их десятками гигабайт на диске, у этой есть очень качественный опенсорсный инструментарий, влезающий в 150 мегабайт на диске www.clifford.at/icestorm. Под Windows тоже есть.

                  Для общения с железкой со стороны компа очень рекомендую писать на яве, используя библиотеку jSerialComm fazecast.github.io/jSerialComm. Исключительно удобно! А для меня так просто спасение, учитывая что я под линуксом, а заказчики в основном под Windows.

                  А вообще сейчас эта платка со связкой верилог + ява — мой главный инструмент для разборок со всяческой электроникой. И Вам советую с неё же начинать. Вот примерно так…
                0
                Вот это я понимаю — программирование!
                А можете что-нибудь похожее для моей Аскоты придумать? У неё есть сложение, вычитание и логика, но нет умножения, деления и десятичного сдвига.
                  0
                  Глянул — жуть! Аж обзавидовался белой и черной завистью! :)))
                  Надо же какие вещи у людей ещё сохранились! Но придумать для неё боюсь что-либо сложно. 50 шагов программы это увы… Наверно можно сделать эмулятор на джаваскрипте, и выложить куда-нибудь для желающих попробовать себя в таком экзотическом деле :)
                  0
                  Правда, при каждом возведении в квадрат у нас удваивается погрешность. Так что нужно выполнять вычисление в формате, у которого количество значащих битов хотя бы вдвое больше желаемой точности.
                    0
                    Давайте считать. Пусть в аккумуляторе оказалось число X с погрешностью d. Если принять Х за 1 (условно), то d будет порядка 2 ^(-17) (один разряд 17-разрядного числа). Возведём в квадрат, пренебрегая квадратом d:
                    (x + d)^2 = x^2 + 2dx, где величина 2dx тоже порядка 2^(-17) и можно сказать что она примерно равна d.
                    Пусть в результате отбрасывания младших разрядов, наложилась новая погрешность d1, тоже порядка 2^(-17) и тоже примерно равная d. Тогда после итерации в аккумуляторе будет число (x1+2d) и т.д. Т.е. (x2+3d), (x3+4d), (x4+5d)…
                    Иными словами погрешность растёт в худшем случае ЛИНЕЙНО, а не экспотенциально (каждый раз удваиваясь), как Вы утверждаете. И при 16 итерациях поражает только 4 младших разряда. На самом деле она растёт ещё медленнее. Но чтобы это доказать, надо лезть в дебри.
                      0
                      После первой итерации — да, 2d*x. А после второй итерации у нас получается (x1+2dx)^2=x1^2+4d*x1, после третьей — (x2+4dx)^2=x2^2+8d*x2 и так далее. Так что всё же экспоненциально.

                      Утверждение, что «2d*x примерно равно d» — это, извините, очень странное утверждение.
                        0
                        Практика показывает что погрешности здесь не растут. Запустите сишный код где считается целочисленная версия и убедитесь. Он там сравнивается с библиотечной функцией log2 и ошибки там на уровне 2^(-16) при 16 разрядах. Признаю, с предыдущим комментарием был не прав. С погрешностями я тут разбирался, но это было 4 года назад. Сейчас несколько подзабыл это дело. Буду разбираться. Но это уже через неделю. Завтра уезжаю и у меня там не будет ничего кроме планшетника и телефона. И интернета тоже не будет.
                    +4

                    Ради прикола, сделал ваш алгоритм в Matlab/Simulink.


                    Тестовая модель с входным и выходным результатом(входные floating point преобразуются в fixed point с умножением на 2^17 и обратно с делением):

                    Сама реализация уже с фиксированной точкой(блок Log2):

                    Результаты моделирования (сигналы U, S, res c модели выше (помечено синим)). Выход компаратора S — это, собственно и есть результат побитно, он сдвигается.


                    Ну и вишенка, HDL код из блока LOG2. Хотите Verilog, хотите VHDL. С комментариями и ссылками, что из каких блоков получилось.


                    Source Code
                    // -------------------------------------------------------------
                    // 
                    // File Name: hdlsrc\log2\Log2.v
                    // Created: 2019-09-29 21:03:43
                    // 
                    // Generated by MATLAB 9.5 and HDL Coder 3.13
                    // 
                    // 
                    // -- -------------------------------------------------------------
                    // -- Rate and Clocking Details
                    // -- -------------------------------------------------------------
                    // Model base rate: 1
                    // Target subsystem base rate: 1
                    // 
                    // -------------------------------------------------------------
                    
                    // -------------------------------------------------------------
                    // 
                    // Module: Log2
                    // Source Path: log2/Log2
                    // Hierarchy Level: 0
                    // 
                    // -------------------------------------------------------------
                    
                    `timescale 1 ns / 1 ns
                    
                    module Log2
                              (clk,
                               In_rsvd,
                               Out_rsvd);
                    
                      input   clk;
                      input   [17:0] In_rsvd;  // ufix18
                      output  [17:0] Out_rsvd;  // ufix18
                    
                      reg [7:0] Counter_Limited_out1;  // uint8
                      wire Compare_To_Constant_out1;
                      wire [35:0] Bit_Shift_out1;  // ufix36
                      wire [35:0] Bit_Shift1_out1;  // ufix36
                      wire [17:0] Bit_Shift1_out1_dtc;  // ufix18
                      wire [17:0] Bit_Shift_out1_dtc;  // ufix18
                      wire S_S;
                      wire [17:0] U_U;  // ufix18
                      reg [17:0] Delay_out1;  // ufix18
                      wire [17:0] U_U_1;  // ufix18
                      wire [35:0] Divide_out1;  // ufix36
                      wire Relational_Operator_relop1;
                      wire [17:0] Data_Type_Conversion1_out1;  // ufix18
                      wire [17:0] res;  // ufix18
                      reg [17:0] Delay1_out1;  // ufix18
                      wire [17:0] Bit_Shift2_out1;  // ufix18
                    
                      initial begin
                        Counter_Limited_out1 = 8'b00000000;
                        Delay_out1 = 18'b000000000000000000;
                        Delay1_out1 = 18'b000000000000000000;
                      end
                    
                      // Count limited, Unsigned Counter
                      //  initial value   = 0
                      //  step value      = 1
                      //  count to value  = 16
                      // 
                      // <S1>/Counter Limited
                      always @(posedge clk)
                        begin : Counter_Limited_process
                          if (Counter_Limited_out1 >= 8'b00010000) begin
                            Counter_Limited_out1 <= 8'b00000000;
                          end
                          else begin
                            Counter_Limited_out1 <= Counter_Limited_out1 + 8'b00000001;
                          end
                        end
                    
                      // <S1>/Compare To Constant
                      assign Compare_To_Constant_out1 = Counter_Limited_out1 == 8'b00000000;
                    
                      // <S1>/Bit Shift1
                      assign Bit_Shift1_out1 = Bit_Shift_out1 >> 8'd1;
                    
                      assign Bit_Shift1_out1_dtc = Bit_Shift1_out1[17:0];
                    
                      assign Bit_Shift_out1_dtc = Bit_Shift_out1[17:0];
                    
                      // <S1>/Switch1
                      assign U_U = (S_S == 1'b0 ? Bit_Shift_out1_dtc :
                                  Bit_Shift1_out1_dtc);
                    
                      // <S1>/Delay
                      always @(posedge clk)
                        begin : Delay_rsvd_process
                          Delay_out1 <= U_U;
                        end
                    
                      // <S1>/Switch
                      assign U_U_1 = (Compare_To_Constant_out1 == 1'b0 ? Delay_out1 :
                                  In_rsvd);
                    
                      // <S1>/Divide
                      assign Divide_out1 = U_U_1 * U_U_1;
                    
                      // <S1>/Bit Shift
                      assign Bit_Shift_out1 = Divide_out1 >> 8'd17;
                    
                      // <S1>/Relational Operator
                      assign Relational_Operator_relop1 = Bit_Shift_out1 > 36'h00003FFFF;
                    
                      assign S_S = Relational_Operator_relop1;
                    
                      // <S1>/Data Type Conversion1
                      assign Data_Type_Conversion1_out1 = {17'b0, S_S};
                    
                      // <S1>/Delay1
                      always @(posedge clk)
                        begin : Delay1_process
                          Delay1_out1 <= res;
                        end
                    
                      // <S1>/Bit Shift2
                      assign Bit_Shift2_out1 = Delay1_out1 <<< 8'd1;
                    
                      // <S1>/Bitwise Operator
                      assign res = Data_Type_Conversion1_out1 | Bit_Shift2_out1;
                    
                      assign Out_rsvd = res;
                    
                    endmodule  // Log2
                    

                    Могу также сгенерить VHDL, кому интересно.
                    Код, возможно, не самый оптимизированный, но должен быть 100%-но рабочий. Синтезатор лишнее все равно выкинет.

                      0
                      Жуть!!! АДЪ и Израиль! :))))))))))
                      Жаль картинки тут плохо видны, трудно понять что за модель. И последний рисунок у Вас неверный. Вы очевидно трактуете res как просто число. Сначала маленькое, но постепенно заполняющее старшие разряды. А тут скорее надо себе представить, что разряды заполняются начиная со старших и вправо. Т.е. первым появляется самый старший разряд, за ним следующий, следующий и т.д. Тогда получится картинка не экспотенциального роста, а экспотенциального приближения к результату, причем строго снизу, без колебаний.
                      С кодом да, любопытно было бы. Кстати что удивительно, он более не менее читаем!!! Вот чего не ожидал… К сожалению Вы забыли реализовать сигналы wr (начинающий вычисление) и rdy (готовность результата). Без них моделировать нельзя. Вычисления надо как-то начать и как-то остановить. Если я полезу сейчас вставлять их ручками, боюсь чего-нибудь напортачу. Да и впечатление будет немного не то.

                      А вообще побаиваюсь я честно говоря таких вещей. Когда я вижу верилог (по крайней мере в той манере в которой пишу сам) я неплохо представляю себе, во что это развернется. А с такой моделью — хрен знает… Впрочем у меня тут сильно испорченное ассемблером прошлое. Я из-за этого и С долго не мог освоить. Мол не понимаю я этих ваших операторов! Скажите что машина делает! Сейчас привык и неплохо себе представляю, во что разворачивается С. Но по той же причине не могу освоить Rust. Для меня очень важная штука — «прозрачность» языка.
                        +1
                        Жаль картинки тут плохо видны, трудно понять что за модель

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


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

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


                        К сожалению Вы забыли реализовать сигналы wr (начинающий вычисление) и rdy (готовность результата). Без них моделировать нельзя.

                        Я не забыл, просто они для моделирования не нужны — у вас же в Си коде их тоже нет? Да и в конечной реализации, когда этот блок будет интегрироваться в большую систему, разработчику может быть они и не понадобятся, если он будет моделировать все вместе.


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

                        Не надо никуда лезть ручками, тем более в сгенерированный код! Если хотите, могу дорисовать пару регистров. Тогда моя модель будет работать как ваш verilog код, и сгенерированный код будет так же bit and cycle-true.


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

                        Просто Verilog — он как ассемблер для ПЛИС. Вы работаете на уровне базовых физических блоков — сигналов, ячеек, регистров. А надо не бояться и здесь переходить на языки более высокого уровня — так вы абстрагируетесь от железа и можете сконцентрироваться на решении основной задачи, получая результат гораздо быстрее. Да и знания Verilog/VHDL тогда становятся не такими критичными — главное, понимать ограничения ПЛИС — задержки и фиксированную запятую.


                        А вообще интересно мнение здешних ITшников — ваш Си код они, конечно, понимают, а понимают ли они представленную модель из Матлаба? Ведь из нее с тем же успехом можно сгенерить как HDL код, так и тот же Си-код для какого-нибудь микроконтроллера, причем хоть 8-и битного, хоть 32-х битного и оно будет работать.

                          0
                          Я не забыл, просто они для моделирования не нужны — у вас же в Си коде их тоже нет?

                          Как бы в С они неявно тоже есть. Сигнал wr — начать вычисления это вызов функции. А rdy — готовность, это выход из функции. Что-то подобное должно быть в любом модуле, действующем с «началом и концом» так сказать. Ладно, прошу прощения, уже собираюсь тут в дорогу. Теперь появлюсь через неделю, вряд ли раньше.
                            +1
                            Как бы в С они неявно тоже есть. Сигнал wr — начать вычисления это вызов функции. А rdy — готовность, это выход из функции. Что-то подобное должно быть в любом модуле, действующем с «началом и концом» так сказать.

                            Ну вы же сами писали:


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

                            То есть начала и конца у данной функции нет, так как она будет сделана в железе из физических ячеек и сигналов и будет молотить постоянно и независимо от других. И "подходящие условия" необязательно должны определяться сигналами wr и RDY. Можно легко сделать так, что новое входное значение для нее будет готово как раз к тому моменту, когда она закончит считать предыдущий логарифм (что, собственно, частый кейс в обработке сигналов) и это мы еще не смотрели на возможность конвейеризации, upsampling и TDM, которую я не моделировал, но должна быть здесь легко возможна — и интерфейсные сигналы будут в этом случае совсем другие.
                            А результат из этой функции может браться опять же ровно через 16 тактов — это же фиксированное число.
                            То есть wr и rdy — это больше условности для интерфейса в "стиле Си" — с вызовом функции сигналом wr и возвращением результата по сигналу rdy. А мышление ПЛИСовода должно быть другим.


                            Но это мое ИМХО, не обращайте внимания.

                      0
                      Это уже где то видел, автор не сбайтил?
                        0
                        Ну если Вы господин Пак, то безусловно. Года 4 назад я Вам это показывал, а Вы очень долго не могли понять как это работает :)))
                        +1
                        Надо протактировать в регистр это (cnt != 0), и только потом использовать многократно по всему модулю.
                          –1
                          Надо протактировать в регистр это (cnt != 0), и только потом использовать многократно по всему модулю.

                          Языки описания аппаратуры(верилог в частности) описывают не программу как последовательность исполняемых команд, а сеть параллельных процессов. Всё выполняется по всей «программе» (на самом деле сети), как только возникли подходящие для этого условия. Что кстати требует от разработчика мышления сильно не такого, как у программистов. Если Вы имеете в виду, что «протактировал» я где-то не в самом начале исходника, это без разницы. Мог бы хоть в самом конце. Я тут как бы скорее не программирую, а расставляю на плате микросхемки и соединяю их проводочками.
                          Вот гляньте книжку www.twirpx.com/file/51125. Очень полезно будет почитать для начала.
                            +1
                            Вы даже не поняли мой коментарий. FPGA это моя специализация последние 17 лет.
                              0
                              Признаюсь честно — да, немного не понял.
                              Надо протактировать в регистр это (cnt != 0), и только потом использовать многократно по всему модулю.

                              Что всё-таки Вы имели в виду?
                                0
                                Вот это

                                always @(posedge clk)
                                if(wr) cnt<=nbits+1;
                                else
                                if(cnt != 0)
                                cnt<=cnt-1;


                                заменить этим.

                                reg cnt_down;
                                always @(posedge clk) cnt_down <= (cnt == 'd1);

                                always @(posedge clk)
                                if(wr) cnt<=nbits+1;
                                else
                                if(!cnt_down )
                                cnt<=cnt-1;


                                Так код чище, легче будет placement & routing.
                                Аналогично

                                always @(posedge clk)
                                if(wr) res<=0;
                                else
                                if(!cnt_down) begin
                                res[nbits-1:1]<=res[nbits-2:0];
                                res[0]<=bit;
                                end


                                И это

                                always @(posedge clk) rdy<=cnt_down;

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

                        Самое читаемое