МАРСОХОД

Open Source Hardware Project

Добро пожаловать, Гость
Логин: Пароль: Запомнить меня
  • Страница:
  • 1
  • 2

ТЕМА: Си как HDL, или Verilog без always

Си как HDL, или Verilog без always 3 года 8 мес. назад #3119

  • Leka
  • Leka аватар Автор темы
  • Не в сети
  • Живу я здесь
  • Живу я здесь
  • Сообщений: 631
  • Спасибо получено: 51
В качестве очередной простой задачки возьмем сортировку чисел. Например, формирование топ-списка(по убыванию) из потока. Нуль будем использовать для очистки топ-списка вытеснением первого из списка. Алгоритм - "лобовой" (первый пришедший в голову), на Си:
#define M 8
int a[M];
extern int d;
sort(){
	int cmp[M],pos[M];
	if(d==0){ 
		for(int i=0;i<M-1;i++) a[i]=a[i+1];
		a[M-1]=d;	
	}
	else{
		for(int i=0;i<M;i++) cmp[i]=d>a[i];
		pos[0]=cmp[0];				
		for(int i=1;i<M;i++) pos[i]=cmp[i-1]^cmp[i];		
		for(int i=M-2;i>=0;i--) if(cmp[i]) a[i+1]=a[i];
		for(int i=0;i<M;i++) if(pos[i]) a[i]=d;
	}
}

Можно откомпилировать, и посмотреть, как работает:
int d;
main(){
	while(1){
		for(int i=0;i<M;i++) printf(" %d ",a[i]);
		printf("> ");
		scanf("%d",&d);
		sort();
	}
}

Попробуем для синтеза переписать sort() на Verilog. А лучше - на SystemVerilog. Даже если не знаете SV, и пишете все на Verilog, все-равно советую поставить в Квартусе галочку "использовать SV" - хотя-бы ради расширенной поддержки многомерных упакованных массивов.
Для примера выше, для "int a[M]" можно объявить " a[M][4][8]" - массив из М 4х-байтовых слов.
a[0] будет адресовать первое 32-разрядное слово в массиве,
a[0][1] - 2-ой байт первого слова в массиве,
a[0][1][2] - 3-ий бит 2-ого байта первого слова в массиве. Удобно!
На байты поделим, чтобы потом проще было дописать обмен по UART, добавим еще сигнал разрешения клока "en".
Прикинемся чайником, и перепишем "в лоб", методом copy/paste:
module sort #(
	M=8,N=4
)(
	output [M-1:0][N-1:0][8-1:0] a,
	input [N-1:0][8-1:0] d,
	input en,clk
);
	bit [M-1:0] cmp,pos;	
	always@(posedge clk)
	if(en)
		if(d==0) begin 
			for(int i=0;i<M-1;i++) a[i]=a[i+1];
			a[M-1]=d;	
		end
		else begin	
			for(int i=0;i<M;i++) cmp[i]=d>a[i];
			pos[0]=cmp[0];				
			for(int i=1;i<M;i++) pos[i]=cmp[i-1]^cmp[i];	
			for(int i=M-2;i>=0;i--) if(cmp[i]) a[i+1]=a[i];
			for(int i=0;i<M;i++) if(pos[i]) a[i]=d;
		end
endmodule
Заработает, как ни странно, но делать так нельзя - нарушены сразу несколько правил безопасного кодинга на Верилоге.

Правильнее будет так:
module sort #(
	M=8,N=4
)(
	output [M-1:0][N-1:0][8-1:0] a,
	input [N-1:0][8-1:0] d,
	input en,clk
);
	bit [M-1:0] cmp,pos;	
	always_comb //или always@*
	begin
		for(int i=0;i<M;i++) cmp[i]=d>a[i];
		pos[0]=cmp[0];				
		for(int i=1;i<M;i++) pos[i]=cmp[i-1]^cmp[i];		
	end 
	
	always_ff@(posedge clk) //или always@(posedge clk)
	if(en)
		if(d==0) begin 
			for(int i=0;i<M-1;i++) a[i]<=a[i+1];
			a[M-1]<=d;	
		end
		else begin		
			for(int i=M-2;i>=0;i--) if(cmp[i]) a[i+1]<=a[i];
			for(int i=0;i<M;i++) if(pos[i]) a[i]<=d;
		end
endmodule
Те регистры - отдельно, "провода" - отдельно.
Для "проводов" - блокирующие присваивания, для регистров - неблокирующие.

А если не заморачиваться, и синтезировать сразу из Си, прогоняя через компилятор в Верилог?
Для схемы важно различать регистры/"провода", поэтому по-любому придется вводить какие-либо новые типы(или конструкции). В своем проекте поступил так: от SV взял новые типы, причем "reg" и "bit" - без изменений, "parameter" сократил до "par", а вместо "input" - "ext", сокращенное от "extern"(Си). Вот что получилось:
par M=8,N=4;
reg [M][N][8] a;
ext [N][8] d;
ext [1] en;
sort(){
	bit [M] cmp,pos;
	if(en)
		if(d==0){ 
			for(int i=0;i<M-1;i++) a[i]=a[i+1];
			a[M-1]=d;	
		}
		else{
			for(int i=0;i<M;i++) cmp[i]=d>a[i];
			pos[0]=cmp[0];				
			for(int i=1;i<M;i++) pos[i]=cmp[i-1]^cmp[i];		
			for(int i=M-2;i>=0;i--) if(cmp[i]) a[i+1]=a[i];
			for(int i=0;i<M;i++) if(pos[i]) a[i]=d;
		}
}
Синтезируется, top.map.summary:
Family : Cyclone III
Total logic elements : 532
    Total combinational functions : 532
    Dedicated logic registers : 256
Total registers : 256
Total pins : 290
И даже работает. За такт выполняется весь код сравнения и вставки.
Спасибо сказали: ali00ff

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Си как HDL, или Verilog без always 3 года 8 мес. назад #3137

  • Leka
  • Leka аватар Автор темы
  • Не в сети
  • Живу я здесь
  • Живу я здесь
  • Сообщений: 631
  • Спасибо получено: 51
В поисках простого примера подумал про калькулятор, первый попавшийся в инете:
panchul.livejournal.com/201168.html
Простой стековый калькулятор, только + и *, ввод/вывод в hex. Такой и попробуем сделать по-быстрому, только ввод сделаем через uart, а вывод на vga, модули возьмем из тетриса. И в десятичном представлении, как раз binary to BCD потребуется.
Сначала напишем и отладим "стековый калькулятор" на Си (на всякий случай, "a" - сложение, "m" - умножение, вместо "enter" - пробел). Консольная программа:
#define N 4
int d[N];
extern char c, en;
calc(){
	static char clr=0;
	if(en)
		if((c>='0')&(c<='9')){
			d[0]=clr?(c&15):(d[0]*10)+(c&15); 
			clr=0;
		}
		else if(c==' '){ 
			for(int i=N-1;i>0;i--) d[i]=d[i-1];
			d[0]=0;
			clr=0;
		}	
		else if((c=='a')|(c=='m')){
			if(c=='a') d[0]=d[0]+d[1];
			else d[0]=d[0]*d[1];
			for(int i=1;i<N-1;i++) d[i]=d[i+1];
			clr=1;
		}
}
#include <conio.h>
char en, c;
main(){
	while(1){
		sleep(10);
		while(kbhit()){ en=1; c=getch(); }
		calc();
		if(en) printf("\r%9d",d[0]);
		en=0;
	}
}
Также напишем и отладим bin2bcd, алгоритм можно в инете почерпнуть (я немного модифицировал, зачем - уже не помню):
#define N 5
#define R 16
int d[N];
extern int b;
bin2bcd(){
	int k;
	for(int j=0;j<N;j++) d[j]=0; 
	for(int i=R-1;i>=0;i--){ 
		k=(b>>i)&1; 
		for(int j=0;j<N;j++){
			if(d[j]>=5) k=k|6; 
			d[j]=(d[j]<<1)+k;  
			k=(k>>2)&1;
			d[j]=d[j]&15;	
		}	
	}			
}
int b;
main(){
	while(1){
		printf("\n");
		for(int j=N-1;j>=0;j--)printf(" %d",d[j]);
		printf(" > ");
		scanf("%d",&b);
		bin2bcd();
	}
}
Copy/paste + правка типов + таблица таблица символов 0..9 --> top.h + pll.v + top.qsf --> top.sof для марсохода2 (проверял на другой плате с другими pll.v + top.qsf).

Если писать/отлаживать алгоритмы на обычном Си с целью переноса на HDL, надо учитывать, какие переменные будут регистрами, а какие "проводами". В обычном Си нет регистров, поэтому код усложняется. Например, для обмена содержимым a <--> b, надо будет писать: "tmp=a; a=b; b=tmp;" , тогда как для регистров можно: "reg a,b; a=b; b=a;" (используя отложенное присваивание).

Вложенный файл:

Имя файла: calc.zip
Размер файла: 21 KB
Вложения:

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Си как HDL, или Verilog без always 3 года 7 мес. назад #3153

  • Leka
  • Leka аватар Автор темы
  • Не в сети
  • Живу я здесь
  • Живу я здесь
  • Сообщений: 631
  • Спасибо получено: 51
Хочу объяснить, почему выбрал именно такой синтаксис, а не другой.

1. Почему порты объявлены так (пример из предыдущего топика):
bit [16] d0; 
ext [8] c;  
ext [1] c_en;  
calc(){ 
	... 
	d0=d[0];	
}
а не так, например:
calc(
	bit [16] d0, 
	ext [8] c,  
	ext [1] c_en 
){
	...
?

Это связано с тем, что аргументы в Си передаются только по значению, и соответствующая программа на Си выглядела бы так:
calc(
	int *d0, 
	char c,  
	char c_en 
){
	... 
	*d0=d[0]; 
}

main(){
	...
	calc(&d0,c,en);
	...
}

Мне такой синтаксис не нравится, и решил объявлять порты, как глобальные переменные.

2. Почему "reg", "bit", "ext" ?

"reg", "bit" - взято из Верилога, но с тем отличием, что "reg" - всегда регистр, а "bit" - всегда провод.
"ext" - от "extern" из Си. Это вход, подключенный к выходу, другого модуля. К одному выходу м/б подключено несколько входов, но не наоборот, и это отражено в "external" - соответствующий выход объявлен в одном внешнем модуле.

3. Почему только упакованные массивы?

1) Так меньше путаницы с количеством и порядком индексов.
2) С многомерными упакованными массивами можно обращаться, как c одномерными, см. SystemVerilog.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Последнее редактирование: от Leka.

Си как HDL, или Verilog без always 3 года 7 мес. назад #3170

  • Leka
  • Leka аватар Автор темы
  • Не в сети
  • Живу я здесь
  • Живу я здесь
  • Сообщений: 631
  • Спасибо получено: 51
Cофт-процессор для демо-примера, исходные требования в порядке приоритета:
1) обязательно - наличие готового Си-компилятора,
2) простота и малые размеры, в тч и по исходникам,
3) достаточное быстродействие.

Подумал, а не достаточно ли будет просто обеспечить совместимость "по ассемблеру" с ядром какого-нибудь из распространенных процессоров? Тогда не нужно делать точный клон, все "неудобные" команды можно выбросить, заменив их псевдо-командами (в тч составными).
Выбор пал на msp430 - понравился генерируемый GCC ассемблерный код.
Для своего проекта максимально упростил, выбросив некоторые команды и некоторые методы адресации.
Фон-Неймановская архитектура, причем регистры расположил вместе с командами и данными - в одной двухпортовой блочной памяти. Все команды выполняются за 2..4 такта: с регистровым методом адресации - 2 такта, с одним индексным (для источника или приемника) - 3 такта, с двумя индексными (для источника и приемника) - 4 такта. Авто-инкремент/декремент выбросил, выбросил также PUSH/POP, CALL/RET (эмулируются через другие команды). Тактовая ~100МГц(CycloneV), map.summary для CycloneIII (без периферии):
Total logic elements : 365
    Total combinational functions : 357
    Dedicated logic registers : 72
Total registers : 72

Блок-схема ядра (core.h):

G - управление и генерация адресов для 2х-портовой памяти,
M - 2х-портовая блочная память (внешний модуль),
B - перестановка старшего байта (для байтовых операций с памятью),
S - расширение знака (для байтовых операций),
N - инверсия,
Z - обнуление,
R - сдвиг вправо,
L - логические операции,
A - сумматор,
W - перестановка байтов,
F - формирование флагов.

Ассемблер свой, Си-компилятор - GCC. Проверил на программе N-ферзей ("printf" заменяется на вывод в порт) - работает (скомпилированный код загружаю через uart, вывод тоже через uart).
int queens(int N) {
    int count=0;
    int arow[32], aleft[32], aright[32], aposs[32];
    int    poss, place, val = (1<<N)-1, pos=1;
    arow[1]=aleft[1]=aright[1]=0;
    poss=aposs[1]=val>>(N/2);
    while(pos){
        if(poss){
            place = poss & -poss; 
            poss &= ~place;
            if(pos==1 && !poss && (N & 1))count<<=1;
            if(pos!=N){
                aposs[pos]=poss;
                poss=arow[pos+1]=arow[pos]|place;
                poss|=aleft[pos+1]=(aleft[pos]|place)<<1;
                poss|=aright[pos+1]=(aright[pos]|place)>>1;
                aposs[++pos]=poss=~(poss) & val;
            } else{
                ++count;
            }
        }else{
            poss=aposs[--pos];
        }
    }
    if(!(N & 1))count<<=1;
    return(count);
}
main(){
    int i, k;
    for(i=0;i<13;i++){
        k=queens(i);
        printf("f(%d)=%d\n",i,k);	
    }	
}
Вложения:

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Последнее редактирование: от Leka.

Си как HDL, или Verilog без always 3 года 7 мес. назад #3232

А можно какую-нибудь задачу из High Performance Computing так обсчитать?

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Последнее редактирование: от alman.

Си как HDL, или Verilog без always 3 года 1 мес. назад #4736

  • Leka
  • Leka аватар Автор темы
  • Не в сети
  • Живу я здесь
  • Живу я здесь
  • Сообщений: 631
  • Спасибо получено: 51
Делаю проект с беспроводным доступом к ПЛИС - в тч для прошивки/загрузки битстрима.
Требования:
1) простота проекта, минимум компонентов,
2) минимальное время прошивки/загрузки,
3) минимальная цена.

Минимум компонентов можно разными способами получить:
а) беспроводный модуль + МК с большой ПП(для хранения сжатого битстрима) + ПЛИС в режиме PS,
б) беспроводный модуль + загрузочная флешка + ПЛИС в режиме AS.
Вариант а) выигрывает по 1) и 2), но проигрывает по 3), тк подходящие МК сравнимы по стоимости с нишевыми ПЛИС. Поэтому выбор пал на вариант б), тем более, что для экспериментов достаточно взять
2 готовых модуля. У меня - bluetooth HC-06 + DE0-nano.

Главная проблема - недостаточная скорость обмена по bluetooth через виртуальный COM-порт.
ПО на стороне ПК пишу на базе CreateFile(), WriteFile(), ReadFile(), обмен с ПЛИС - пакетами разной длины. Получилось ~20КБайт/сек на больших пакетах(несколько КБайт), и 8мсек/пакет на маленьких пакетах(несколько байт). Запись в EPCS производится только блоками по 256 байт, с паузой в несколько мсек. С пакетами в 256 байт получается ~8КБайт/сек, в итоге прошивается очень долго.

Выход - передавать сжатый битстрим блоками по несколько КБайт, с использование буферной памяти в ПЛИС. Решил переделать все с использованием мелкого софт-процессора, взял свое упрощенное 430 ядро(см выше в этой ветке). Программа в софт-ядро загружается по тому-же беспроводному каналу. Система получилась в ~600 LE - с аппаратным UART(программно нельзя, тк по нему идет загрузка самой программы). Ножками EPCS пока дергаю программно (можно добавить аппаратный SPI).

К чему пишу - может, кто чего посоветует.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Си как HDL, или Verilog без always 3 года 1 мес. назад #4738

20 Килобайт в секунду это вполне приемлемая скорость. Мой проект закгрузился бы за 18 секунд, если заливать непрерывно.
ZIP позволил сократить размер SOF файла где-то в три раза. Ну да, некий смысл в этом есть.
А новая прошивка не затиривает "софт-процессор"?

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Си как HDL, или Verilog без always 3 года 1 мес. назад #4739

  • Leka
  • Leka аватар Автор темы
  • Не в сети
  • Живу я здесь
  • Живу я здесь
  • Сообщений: 631
  • Спасибо получено: 51
Для AS/PS надо не SOF, а RPD/POF. Для мелких проектов с малым заполнением ПЛИС в файле в основном нули, можно сжимать в 5-6 раз даже более простыми алгоритмами. И больший смысл именно в быстрой загрузке мелких проектов, тк они быстро компилируются, и длительная загрузка будет раздражать. А для больших проектов, компилируемых за несколько минут, разница в десяток-другой секунд непринципиальна.

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

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Си как HDL, или Verilog без always 3 года 1 мес. назад #4740

  • Leka
  • Leka аватар Автор темы
  • Не в сети
  • Живу я здесь
  • Живу я здесь
  • Сообщений: 631
  • Спасибо получено: 51
Уточнение, для AS нужен RPD, получается последовательно: SOF --> POF --> RPD.
Для PS вроде нужен RBF, но этот вариант не пробовал.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Си как HDL, или Verilog без always 3 года 2 нед. назад #4778

  • Leka
  • Leka аватар Автор темы
  • Не в сети
  • Живу я здесь
  • Живу я здесь
  • Сообщений: 631
  • Спасибо получено: 51
В софт-процессоре ошибка оказалась - байтовые команды неправильно отрабатывались, поправил.
Сжатие "на лету" нулевых последовательностей заметно повышает скорость передачи битстрима, для почти пустого проекта - на порядок. Кодирование - при помощи esc-последовательностей, только в качестве префикса взял не <27>, а <121>.
<121><0...2> - аппаратное управление софт-процессором (загрузка программы, пуск-останов),
<121><3...251> - программное сжатие нулевых строк (>2 нулей подряд),
<121><252...254> - программное управление потоком (ПК <--> софт-процессор),
<121><255> - сам префикс <121>.
Основная сложность - со стороны ПК(спасибо Wintel), не знаю, как проверять - поступили ли новые данные через виртуальный СОМ-порт, или нет. Это усложняет протокол взаймодействия с устройством.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

  • Страница:
  • 1
  • 2
Время создания страницы: 0.428 секунд

facebook  GitHub  YouTube  Twitter
Вы здесь: Начало Forum Наш форум Проекты пользователей Си как HDL, или Verilog без always