Jump to content

    

verilog, использование встроенной ОЗУ MAX10

Доброго времени! Прошу совета у сообщества. "Сам я в верилог недавно".

Пытаюсь таким образом:

`timescale 1 ps / 1 ps
module sram_adapter
#(parameter gp_SRAM_D_WIDTH = 8, parameter gp_SRAM_A_WIDTH = 12, 
	parameter gp_SRAM_WORD_SIZE = 1<<gp_SRAM_A_WIDTH)
	(
		input [(gp_SRAM_D_WIDTH-1):0] i_d0, input [(gp_SRAM_D_WIDTH-1):0] i_d1,
		input [(gp_SRAM_A_WIDTH-1):0] i_add0, input[(gp_SRAM_A_WIDTH-1):0] i_add1, input[(gp_SRAM_A_WIDTH-1):0] i_add2,
		input i_clock,
		input i_add0bus_sel, input i_add1bus_sel, input i_add2bus_sel,
		input i_rden0, input i_rden1,
		input i_wren0, input i_wren1, 
		input i_noe_uc, input i_nwe_uc, input i_ncs_uc,
		input [(gp_SRAM_A_WIDTH-1):0] i_add_uc,
		output wire[(gp_SRAM_D_WIDTH-1):0] o_d0, 
		output wire	[(gp_SRAM_A_WIDTH-1):0] o_add, 
		inout [(gp_SRAM_D_WIDTH-1):0] io_d_uc, 
		output dbg0,
		output dbg1, 
		output dbg2
	);

reg [(gp_SRAM_A_WIDTH-1):0] r_a; 
reg[(gp_SRAM_D_WIDTH-1):0]  r_d; reg[(gp_SRAM_D_WIDTH-1):0]  r_q; 

assign o_add = r_a;

assign o_d0 = r_q;

reg [3:0]r_bus_mux_sel, r_we, r_oe;
assign dbg0 = (r_oe);
assign dbg1 = (r_we);
assign dbg2 = |r_bus_mux_sel;

reg [gp_SRAM_D_WIDTH:0] sram[(gp_SRAM_WORD_SIZE-1):0];

initial begin
	r_a = 0; r_q = 0;
	r_oe = 0; r_we = 0;
end

// Tri-State Buffer control 
assign io_d_uc = (r_bus_mux_sel[3] && !i_noe_uc &&  i_nwe_uc) ?  r_q: 8'bz;


always @(negedge i_clock) begin
	r_bus_mux_sel[3] <= !i_ncs_uc;
	
	r_bus_mux_sel[0] <= i_add0bus_sel;
	r_bus_mux_sel[1] <= i_add1bus_sel;
	r_bus_mux_sel[2] <= i_add2bus_sel;	
end

always @(posedge i_clock) begin		
	if(r_we) sram[r_a] <= r_d;		
	if(r_oe) r_q <= sram[r_a];		
end


localparam lp_TX1BUF_ADD_OFFSET = 12'd16, lp_RX1BUF_ADD_OFFSET = 12'd96;	
always @(i_add_uc or i_add0 or i_add1 or i_add2 or r_bus_mux_sel) begin
	casex (r_bus_mux_sel) 
		4'd1: r_a = i_add0;
		4'd2: r_a = i_add1 + lp_TX1BUF_ADD_OFFSET;
		4'd4: r_a = i_add2 + lp_RX1BUF_ADD_OFFSET;
		4'b1xxx: r_a = i_add_uc;
		default: r_a = 12'd4095; 
	endcase
end	


always @(i_d0 or i_d1 or r_bus_mux_sel) begin	
	casex (r_bus_mux_sel)
		4'd1: r_d = i_d0;
		4'd4: r_d = i_d1;
		4'b1xxx: r_d = io_d_uc;
		default: r_d = 0; 
	endcase
end

always @(i_wren0 or i_wren1 or i_nwe_uc or r_bus_mux_sel) begin	
	casex (r_bus_mux_sel)
		4'd1: r_we = i_wren0;
		4'd4: r_we = i_wren1;
		4'b1xxx: r_we = !i_nwe_uc;
		default: r_we = 0; 
	endcase
end
always @(i_rden0 or i_rden1 or i_noe_uc or r_bus_mux_sel) begin	
	casex (r_bus_mux_sel)
		4'd1: r_oe = i_rden0;
		4'd2: r_oe = i_rden1;
		4'b1xxx: r_oe = (!i_noe_uc && i_nwe_uc);
		default: r_oe = 0; 
	endcase
end

endmodule

К памяти подключен внешний МК:

 i_noe_uc, input i_nwe_uc, input i_ncs_uc, i_add_uc, io_d_uc

и внутренние "читатели/писатели"(1 писатель/читатель, 1 читатель, 1 писатель).

Результат использования такой:

- синтезируется ОЗУ, запрошенного объема 4096 байт;

- тесты записи/чтения по всему объему со стороны МК проходят успешно;

- внутренние "читатели/писатели" читают и пишут успешно;

НО, как-будто это две разные ОЗУ.

Т.е. если записать МК какие-либо данные в определенный адрес (диапазон адресов), то внутренний читатель вычитывает с этого адреса (адресов) исключительно, то, что туда записывает внутренний писатель. Равно как и МК вычитывает, только то, что сам записал.

Share this post


Link to post
Share on other sites
1 час назад, michael.sysoev сказал:

Пытаюсь таким образом

Давайте с самого начала.

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

Ни названий сигналов, ни комментариев в тексте о том что и где делается.

Ну а нам-то что делать? Нам тоже свое время приходится жалеть. 

Share this post


Link to post
Share on other sites
25 минут назад, iosifk сказал:

Давайте с самого начала.

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

Ни названий сигналов, ни комментариев в тексте о том что и где делается.

Ну а нам-то что делать? Нам тоже свое время приходится жалеть. 

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

`timescale 1 ps / 1 ps
module sram_adapter
#(parameter gp_SRAM_D_WIDTH = 8, parameter gp_SRAM_A_WIDTH = 12, 
	parameter gp_SRAM_WORD_SIZE = 1<<gp_SRAM_A_WIDTH)
	(
		input [(gp_SRAM_D_WIDTH-1):0] i_d0, input [(gp_SRAM_D_WIDTH-1):0] i_d1, //шины данных от внутренних писателей/читателей
		input [(gp_SRAM_A_WIDTH-1):0] i_add0, input[(gp_SRAM_A_WIDTH-1):0] i_add1, input[(gp_SRAM_A_WIDTH-1):0] i_add2,//шины адреса от внутренних писателей/читателей
		input i_clock,//тактовая 
		input i_add0bus_sel, input i_add1bus_sel, input i_add2bus_sel,//сигналы выбора шины от внутренних писателей/читателей
		input i_rden0, input i_rden1,//стробы чтения от внутренних писателей/читателей
		input i_wren0, input i_wren1,//стробы записи от внутренних писателей/читателей 
		input i_noe_uc, input i_nwe_uc, input i_ncs_uc,//стробы чтения - записи - выборки от МК
		input [(gp_SRAM_A_WIDTH-1):0] i_add_uc,//шина адреса от МК
		output wire[(gp_SRAM_D_WIDTH-1):0] o_d0,//выходная шина данных для внутренних потребителей 
		output wire	[(gp_SRAM_A_WIDTH-1):0] o_add,//выходная шина адреса для внутренних потребителей  
		inout [(gp_SRAM_D_WIDTH-1):0] io_d_uc,//шина данных МК 
		output dbg0,//отладочные 
		output dbg1, 
		output dbg2
	);

reg [(gp_SRAM_A_WIDTH-1):0] r_a;//регистр адреса 
reg[(gp_SRAM_D_WIDTH-1):0]  r_d; reg[(gp_SRAM_D_WIDTH-1):0]  r_q;//регистры данных входной - выходной

assign o_add = r_a;
assign o_d0 = r_q;

reg [3:0]r_bus_mux_sel;//управляющий сигнал мультиплексоров шин адреса, шин данных, стробов 
reg r_we, r_oe;//стробы записи - чтения
assign dbg0 = (r_oe);//отладочные
assign dbg1 = (r_we);
assign dbg2 = |r_bus_mux_sel;

reg [gp_SRAM_D_WIDTH:0] sram[(gp_SRAM_WORD_SIZE-1):0];//собственно память

initial begin

	r_a = 0; r_q = 0;
	r_oe = 0; r_we = 0;
end

always @(negedge i_clock) begin//захват сигналов выбора 
	r_bus_mux_sel[3] <= !i_ncs_uc;//строб МК с инверсией 	
	r_bus_mux_sel[0] <= i_add0bus_sel;//строб внутреннего потребителя  0 
	r_bus_mux_sel[1] <= i_add1bus_sel;//строб внутреннего потребителя  1
	r_bus_mux_sel[2] <= i_add2bus_sel;//строб внутреннего потребителя  2	
end
 
assign io_d_uc = (r_bus_mux_sel[3] && !i_noe_uc &&  i_nwe_uc) ?  r_q: 8'bz;// Tri-State Buffer control


always @(posedge i_clock) begin//обращение к памяти		
	if(r_we) sram[r_a] <= r_d;//запись		
	if(r_oe) r_q <= sram[r_a];//чтение		
end


localparam lp_TX1BUF_ADD_OFFSET = 12'd16, lp_RX1BUF_ADD_OFFSET = 12'd96;	

always @(i_add_uc or i_add0 or i_add1 or i_add2 or r_bus_mux_sel) begin//мультиплексор шины адреса
	casex (r_bus_mux_sel)//выбираем 
		4'd1: r_a = i_add0;//шину адреса внутреннего потребителя  0
		4'd2: r_a = i_add1 + lp_TX1BUF_ADD_OFFSET;//шину адреса внутреннего потребителя  1 со смещением
		4'd4: r_a = i_add2 + lp_RX1BUF_ADD_OFFSET;//шину адреса внутреннего потребителя  2 со смещением
		4'b1xxx: r_a = i_add_uc;//шину МК
		default: r_a = 12'd4095;//по умолчанию максимальное значение 
	endcase
end	


always @(i_d0 or i_d1 or r_bus_mux_sel) begin//мультиплексор шины данных	
	casex (r_bus_mux_sel)//выбираем
		4'd1: r_d = i_d0;//входную шину данных внутреннего потребителя  0
		4'd4: r_d = i_d1;//входную шину данных внутреннего потребителя  1
		4'b1xxx: r_d = io_d_uc;//входную шину данных МК
		default: r_d = 0;//по умолчанию 0
	endcase
end

always @(i_wren0 or i_wren1 or i_nwe_uc or r_bus_mux_sel) begin//мультиплексор строба записи	
	casex (r_bus_mux_sel)//выбираем
		4'd1: r_we = i_wren0;//строб записи внутреннего потребителя  0
		4'd4: r_we = i_wren1;//строб записи внутреннего потребителя  2
		4'b1xxx: r_we = !i_nwe_uc;//строб записи МК
		default: r_we = 0;//по умолчанию неактивен 
	endcase
end
always @(i_rden0 or i_rden1 or i_noe_uc or r_bus_mux_sel) begin//мультиплексор строба чтения	
	casex (r_bus_mux_sel)//выбираем
		4'd1: r_oe = i_rden0;//строб чтения внутреннего потребителя  0
		4'd2: r_oe = i_rden1;//строб чтения внутреннего потребителя  1
		4'b1xxx: r_oe = (!i_noe_uc && i_nwe_uc);//строб чтения МК
		default: r_oe = 0;//по умолчанию неактивен 
	endcase
end

endmodule



 

Share this post


Link to post
Share on other sites
29 минут назад, michael.sysoev сказал:

Попробую еще раз, 

Ну еще раз.

Если "шины данных от внутренних писателей/читателей" то приведенный модуль не верхний в проекте, но тогда в нем нельзя делать тристэйте выходы.

Надо убрать все "negedge i_clock" - это не нужно.

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

Т.е. так:

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

мультиплексор адреса

арбитр доступа к памяти - это если микроконтроллер медленный или имеет сигнал "готовность"

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

Share this post


Link to post
Share on other sites
45 minutes ago, michael.sysoev said:

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


always @(i_add_uc or i_add0 or i_add1 or i_add2 or r_bus_mux_sel) begin//мультиплексор шины адреса
	casex (r_bus_mux_sel)//выбираем 
		4'd1: r_a = i_add0;//шину адреса внутреннего потребителя  0
		4'd2: r_a = i_add1 + lp_TX1BUF_ADD_OFFSET;//шину адреса внутреннего потребителя  1 со смещением
		4'd4: r_a = i_add2 + lp_RX1BUF_ADD_OFFSET;//шину адреса внутреннего потребителя  2 со смещением
		4'b1xxx: r_a = i_add_uc;//шину МК
		default: r_a = 12'd4095;//по умолчанию максимальное значение 
	endcase
end	
always @(i_d0 or i_d1 or r_bus_mux_sel) begin//мультиплексор шины данных	
	casex (r_bus_mux_sel)//выбираем
		4'd1: r_d = i_d0;//входную шину данных внутреннего потребителя  0
		4'd4: r_d = i_d1;//входную шину данных внутреннего потребителя  1
		4'b1xxx: r_d = io_d_uc;//входную шину данных МК
		default: r_d = 0;//по умолчанию 0
	endcase
end
always @(i_rden0 or i_rden1 or i_noe_uc or r_bus_mux_sel) begin//мультиплексор строба чтения	
	casex (r_bus_mux_sel)//выбираем
		4'd1: r_oe = i_rden0;//строб чтения внутреннего потребителя  0
		4'd2: r_oe = i_rden1;//строб чтения внутреннего потребителя  1
		4'b1xxx: r_oe = (!i_noe_uc && i_nwe_uc);//строб чтения МК
		default: r_oe = 0;//по умолчанию неактивен 
	endcase
end

 

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

Второе, попробуйте записать МК все единицы в память а потом это считать ПЛИСой и наоборот.

И третье, не обессудьте, два ярчайших примера: некоменнтированый код и оверкоментированный :) Коментов должно быть в меру ;)

Share this post


Link to post
Share on other sites
8 минут назад, Nick_K сказал:

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

Извините, не увидел, что именно не указал в списке чувствительности? 

 

11 минут назад, Nick_K сказал:

Второе, попробуйте записать МК все единицы в память а потом это считать ПЛИСой и наоборот.

Как раз с этим и проблема. Процитирую сам себя:

"Т.е. если записать МК какие-либо данные в определенный адрес (диапазон адресов), то внутренний читатель вычитывает с этого адреса (адресов) исключительно, то, что туда записывает внутренний писатель. Равно как и МК вычитывает, только то, что сам записал."

 

13 минут назад, Nick_K сказал:

И третье, не обессудьте, два ярчайших примера: некоменнтированый код и оверкоментированный :) Коментов должно быть в меру ;)

Известно - перебор, хуже недобора. 

Share this post


Link to post
Share on other sites
17 minutes ago, michael.sysoev said:

Как раз с этим и проблема. Процитирую сам себя:

"Т.е. если записать МК какие-либо данные в определенный адрес (диапазон адресов), то внутренний читатель вычитывает с этого адреса (адресов) исключительно, то, что туда записывает внутренний писатель. Равно как и МК вычитывает, только то, что сам записал."

В моём понимании диапазон - это от 0, к примеру, и до 100. А я говорил про полную запись от начала и до конца. Но это я видно не так понял Вас.

 

18 minutes ago, michael.sysoev said:

Извините, не увидел, что именно не указал в списке чувствительности? 


always @(i_d0 or i_d1 or r_bus_mux_sel) begin	// io_d_uc?
	casex (r_bus_mux_sel)
		4'd1: r_d = i_d0;
		4'd4: r_d = i_d1;
		4'b1xxx: r_d = io_d_uc;
		default: r_d = 0;
	endcase
end
always @(i_rden0 or i_rden1 or i_noe_uc or r_bus_mux_sel) begin	// i_nwe_uc?
	casex (r_bus_mux_sel)
		4'd1: r_oe = i_rden0;
		4'd2: r_oe = i_rden1;
		4'b1xxx: r_oe = (!i_noe_uc && i_nwe_uc);
		default: r_oe = 0;
	endcase
end

Ну и @iosifk по-делу много сказал.

Share this post


Link to post
Share on other sites
15 часов назад, Nick_K сказал:

В моём понимании диапазон - это от 0, к примеру, и до 100. А я говорил про полную запись от начала и до конца. Но это я видно не так понял Вас.

Произвожу с МК тест на целостность ОЗУ путем записи 16 байт случайных данных, с последующим вычитыванием и сравнением контрольных сумм - и так по всему объему. Со стороны ПЛИС также при старте делаю запись/чтение, но там тест попроще и не по всему объему.

 

Списки чувствительности поправил, спасибо (лучше не стало). 

Share this post


Link to post
Share on other sites

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

Сделайте тестбенч в симуляторе, только полный. промоделируйте запись/чтение со стороны МК и с вашей системы. Тогда вам станет доступны все внутренности и ошибка сразу всплывет)

Share this post


Link to post
Share on other sites
В 07.06.2019 в 09:39, des00 сказал:

Сделайте тестбенч в симуляторе, только полный. промоделируйте запись/чтение со стороны МК и с вашей системы. Тогда вам станет доступны все внутренности и ошибка сразу всплывет)

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

Edited by michael.sysoev

Share this post


Link to post
Share on other sites
1 hour ago, michael.sysoev said:

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

Подсунуть отсинтезированную модель и если и там все хорошо(вероятность этого 99.5%), то значит у вас проблемы явно не с ПЛИС, а с окружением. В частности внутри МК

Share this post


Link to post
Share on other sites
В 06.06.2019 в 11:57, michael.sysoev сказал:

К памяти подключен внешний МК и внутренние "читатели/писатели"(1 писатель/читатель, 1 читатель, 1 писатель).

Однако в коде не видно как микроконтроллер синхронизируется с внутренним клоком и не видно арбитра... Кто определяет кому читать-писать, ведь память-то не двухпортовая, а обычная D - Q?

Share this post


Link to post
Share on other sites
3 часа назад, des00 сказал:

В частности внутри МК

Вы оказались совершенно правы, а меня подвела инерция мЫшления. Уж со стороны МК-то я подвоха не ожидал.

Share this post


Link to post
Share on other sites
19 часов назад, iosifk сказал:

Однако в коде не видно как микроконтроллер синхронизируется с внутренним клоком и не видно арбитра

На данном этапе арбитраж между внутренними потребителями не нужен (пока они жестко разнесены по времени). Арбитраж с МК по задумке должна обеспечивать конструкция вида: 

always @(i_add_uc or i_add0 or i_add1 or i_add2 or r_bus_mux_sel) //мультиплексор шины адреса
begin
	casex (r_bus_mux_sel)//выбираем 
		4'd1: r_a = i_add0;//шину адреса внутреннего потребителя  0
		4'd2: r_a = i_add1 + lp_TX1BUF_ADD_OFFSET;//шину адреса внутреннего потребителя  1 со смещением
		4'd4: r_a = i_add2 + lp_RX1BUF_ADD_OFFSET;//шину адреса внутреннего потребителя  2 со смещением
		4'b1xxx: r_a = i_add_uc;//шину МК
		default: r_a = 12'd4095;//по умолчанию максимальное значение 
	endcase
end

приоритет у МК. 

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

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now