nabbla (nabbla1) wrote,
nabbla
nabbla1

Балансируем конвейер QuatCore

В пятницу у нас всё замечательно сработало на симуляции, первые 16 миллисекунд полёт нормальный. А вот прошить весь проект на ПЛИС и попробовать "в железе" ни в пятницу, ни в понедельник не вышло - опять завалили тайминги!

Максимально допустимая частота составила 23,2 МГц, а мне-то нужно 25! Можно было рискнуть, но хочется уж, чтобы "всё железно", не играть в оверклокеров. Да, схема опять подросла, в первую очередь я увеличил размер элементов в буферах FIFO, 10 на вход и 8 на выход. Вообще, это не должно нам увеличивать "комбинаторные пути", но, во-первых, некоторые сигналы становятся слишком разветвлёнными (большой Fanout), то бишь от них управляется одновременно много логических элементов, и фиттеру приходится задействовать дополнительные ЛЭ, просто в качестве буферных, чтобы хватило мощности. Но результатом становится увеличенная задержка.

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

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

По такому случаю пора внести ещё одно изменение в этот процессор, которое очень давно назревало, но очень не хотелось этого делать. А именно, переиначить шину DestAddr, чтобы она выражала не текущий адрес, а БУДУЩИЙ, тот, что появятся на следующем такте. Это позволит всем модулям (Mem, ALU, PC и другие) провести декодирование команды тактом ранее, укоротив комбинаторные пути на "основном этапе". Особенно это хорошо видно по модулю QuatCoreMem:

IMG20210408193008.jpg

(изобразил на бумаге модули QuatCoreRom и QuatCoreMemDecoder, чтобы понять "длину комбинаторных путей")

Огромный разбаланс в том, что на "такте 2" сигнал просто переходит с регистра на регистр (такие пересылки могут выполняться на 167 МГц на этой ПЛИС, мегахалява), зато на следующем такте начинается "мясо", когда управляющие сигналы
CountSP (разрешение на инкремент/декремент стека),
BaseAddr (выбор базового адреса, X,Y,Z или SP),
FirstIndex (выбор первого индекса, 0, 2i, 2j или 4j/-1/Treug[j] для X/Z, Y и SP соотв),
SecondIndex (выбор второго индекса, 1, i, k или 0/i^j для SP и всех остальных)
WriteX, WriteY, WriteZ, WriteSP

должны сформироваться на основе DestAddr и SrcAddr!


На верилоге модуль QuatCoreRom выглядит так:

module QuatCoreCodeROMwSTALL (	input [RomAddrWidth-1:0] Addr, input clk, input stall,
				output [7:0] SrcAddr, output reg [7:0] DestAddr = 8'h89);

	parameter RomAddrWidth = 8;

	wire [15:0] OP;
	reg [15:0] rOP = 16'h8900;

	always @(posedge clk) if (~stall) begin
		rOP <= OP;		
		DestAddr <= rOP[15:8];
	end
		
	assign SrcAddr =  rOP[7:0];

	lpm_rom	lpm_rom_component (
				.address (Addr),
				.q (OP),
				.memenab (1'b1));
	defparam
		lpm_rom_component.intended_device_family = "FLEX10KE",
		lpm_rom_component.lpm_address_control = "UNREGISTERED",
		lpm_rom_component.lpm_file = "QuatCoreCode.mif",
		lpm_rom_component.lpm_outdata = "UNREGISTERED",
		lpm_rom_component.lpm_type = "LPM_ROM",
		lpm_rom_component.lpm_width = 16,
		lpm_rom_component.lpm_widthad = RomAddrWidth;
		
endmodule


Именно здесь так бездарно застревает на один такт DestAddr. Но "зато" на следующем такте с него спрос огромный, в модуле QuatCoreMemDecoder:

module QuatCoreFastMemDecoder (input clk, input [7:0] DestAddr, input [7:0] SrcAddr, input SrcStall, input DestStall, input SrcDiscard,
				output MemWrite, output SrcSquareBrac, output WriteX, output WriteY, output WriteZ, output WriteSP, output CountSP, output SPup,
				output [1:0] BaseAddr, output [1:0] FirstIndex, output [1:0] SecondIndex, output busy	);

reg fetching = 1'b0;
							
wire isDest = (~DestStall)&(DestAddr[7:6] == 2'b11);
wire DestSquareBrac = (DestAddr[3:0] != 4'b1101);
assign MemWrite = isDest & DestSquareBrac;

assign BaseAddr = 		MemWrite? DestAddr[5:4] : SrcAddr[5:4];
assign FirstIndex = 	MemWrite? DestAddr[1:0] : SrcAddr[1:0];
assign SecondIndex = 	MemWrite? DestAddr[3:2] : SrcAddr[3:2];

assign SrcSquareBrac = (SrcAddr[3:0] != 4'b1101);

assign WriteX = isDest & (~DestSquareBrac) & (DestAddr[5:4] == 2'b00);
assign WriteY = isDest & (~DestSquareBrac) & (DestAddr[5:4] == 2'b01);
assign WriteZ = isDest & (~DestSquareBrac) & (DestAddr[5:4] == 2'b10);
assign WriteSP = isDest & (~DestSquareBrac) & (DestAddr[5:4] == 2'b11);

wire isSource = (~SrcDiscard)&(~SrcStall)&(SrcAddr[7:6] == 2'b11);
assign CountSP = ((isDest & DestAddr[5] & DestAddr[4] & DestAddr[1] & DestAddr[0]) | (fetching & isSource & SrcAddr[5] & SrcAddr[4] & SrcAddr[1] & SrcAddr[0]));
assign SPup = ~SecondIndex[0];

assign busy = isSource & SrcSquareBrac & (~fetching);

//осталось с ним сообразить. Когда приходит SrcAddr[7:6] == 2'b11 и SrcDiscard = 0, то невзирая на SrcStall, мы уже начали выборку! 
//и значит, когда нам "позволят" работать, уже получим результат!
//но ещё, когда SrcStall=0 и fetching=1, он должен сброситься опять в ноль,
//чтобы идущая сразу следом команда не могла ошибочно выдать результат СРАЗУ
always @(posedge clk)
	fetching <= (~SrcDiscard)&(SrcAddr[7:6] == 2'b11)&SrcSquareBrac&((~fetching)|SrcStall);
													
endmodule


И ведь это не конец "комбинаторных путей": BaseAddr, FirstIndex и SecondIndex подаются на мультиплексоры, а с их выходов идут сумматоры, и только этот "эффективный адрес" наконец-то защёлкивается на входе в оперативную память.

Похоже, именно это сейчас является узким местом нашего процессора!

Самое приятное, что и сигнал DestStall у нас приходит с задержкой! Это мы тоже изобразили на рисунке, он формируется из сигналов SrcDiscard и DestStallReq, но "вступает в силу" только на следующем такте!

Именно это позволяет провести "балансировку" малой кровью. Не будем даже переводить ВСЕ модули QuatCore на новый интерфейс. Если опять в тайминги не уместимся - вот тогда да.

Первым делом добавляем выход PreDestAddr в QuatCoreRom. "Pre" должно означать, что этот адрес мы как бы выдали "загодя", авансом, и не нужно торопиться его исполнять :)

module QuatCoreCodeROMwSTALL (	input [RomAddrWidth-1:0] Addr, input clk, input stall,
				output [7:0] SrcAddr, output reg [7:0] DestAddr = 8'h89,
				output [7:0] PreDestAddr);

	parameter RomAddrWidth = 8;

	wire [15:0] OP;
	reg [15:0] rOP = 16'h8900;
	
	assign PreDestAddr = rOP[15:8];
	assign SrcAddr =  rOP[7:0];	
	
	always @(posedge clk) if (~stall) begin
		rOP <= OP;		
		DestAddr <= rOP[15:8];
	end
	
	lpm_rom	lpm_rom_component (
				.address (Addr),
				.q (OP),
				.memenab (1'b1));
	defparam
		lpm_rom_component.intended_device_family = "FLEX10KE",
		lpm_rom_component.lpm_address_control = "UNREGISTERED",
		lpm_rom_component.lpm_file = "QuatCoreCode.mif",
		lpm_rom_component.lpm_outdata = "UNREGISTERED",
		lpm_rom_component.lpm_type = "LPM_ROM",
		lpm_rom_component.lpm_width = 16,
		lpm_rom_component.lpm_widthad = RomAddrWidth;
		
endmodule


Но еще у нас есть дурацкий модуль с очень длинным названием, QuatCoreDestStallGenerator. Вообще, мы придерживаемся конвенции Станислава Лема, где за особые заслуги от имени отрезают кусок за куском. И один из самых уважаемых - учитель Ах. Так и здесь, весь процессор это QuatCore, а чем мельче составная часть - тем длиннее её имя!

В общем, вот он, этот маленький но гордый модуль:

module QuatCoreDestStallGenerator (input clk, input DestStallReq, input SrcDiscard, output reg DestStall = 1'b0);

always @(posedge clk) begin
	DestStall <= SrcDiscard | DestStallReq;
end

endmodule


(его мы тоже изобразили на рисунке!)

Ему мы тоже приделаем дополнительный выход, PreDestStall:
module QuatCoreDestStallGenerator (input clk, input DestStallReq, input SrcDiscard, output reg DestStall = 1'b0, output PreDestStall);

assign PreDestStall = SrcDiscard | DestStallReq;

always @(posedge clk) begin
	DestStall <= PreDestStall;
end

endmodule


Далее, мы заменим входы DestAddr и DestStall в QuatCoreMemDecoder на PreDestAddr и PreDestStall. Думаю, входы WriteX, WriteY, WriteZ, WriteSP совсем уж переносить в предыдущий такт не требуется, "защёлкнем" только RegWrite = isDest & (~DestSquareBrac), и этого хватит:

//приготовление эффективного адреса и получение данных по этому адресу - процесс небыстрый,
//поэтому разбиваем его на 2 такта. Т.е если у нас запрашивают ЧТЕНИЕ из памяти, то включаем busy на 1 такт

//на удивление сложное поведение внутри конвейера, нужно различать 3 разных управляющих сигнала:
//DestStall запрещает запись в регистры, память и инкременты/декременты SP, по части DestAddr
//SrcStall запрещает инкременты/декременты SP, но как ни в чём не бывало формирует правильное значение в DataBus. Когда будет SrcStall=0, мы тут же выдаём ответ
//SrcDiscard отличается тем, что после сброса в 0 мы выжидаем ещё 1 такт, прежде чем выдать ответ, т.к не уверены, что адрес команды остался тем же. 

module QuatCoreFasterMemDecoder (input clk, input [7:0] PreDestAddr, input [7:0] SrcAddr, input SrcStall, input PreDestStall, input SrcDiscard,
				output reg MemWrite = 1'b0, output SrcSquareBrac, output WriteX, output WriteY, output WriteZ, output WriteSP, output CountSP, output SPup,
				output [1:0] BaseAddr, output [1:0] FirstIndex, output [1:0] SecondIndex, output busy	);

reg fetching = 1'b0;

reg RegWrite = 1'b0;
reg DestCountSP = 1'b0;
reg [5:0] DestAddr = 6'h00;
							
wire isDest = (~PreDestStall)&(PreDestAddr[7:6] == 2'b11);
wire DestSquareBrac = (PreDestAddr[3:0] != 4'b1101);

always @(posedge clk) begin
	MemWrite <= DestSquareBrac & isDest;
	RegWrite <= (~DestSquareBrac) & isDest;
	DestCountSP <= isDest & PreDestAddr[5] & PreDestAddr[4] & PreDestAddr[1] & PreDestAddr[0];
	DestAddr <= PreDestAddr[5:0];
end

assign BaseAddr = 	MemWrite? DestAddr[5:4] : SrcAddr[5:4];
assign FirstIndex = 	MemWrite? DestAddr[1:0] : SrcAddr[1:0];
assign SecondIndex = 	MemWrite? DestAddr[3:2] : SrcAddr[3:2];

assign SrcSquareBrac = (SrcAddr[3:0] != 4'b1101);

assign WriteX = RegWrite & (DestAddr[5:4] == 2'b00);
assign WriteY = RegWrite & (DestAddr[5:4] == 2'b01);
assign WriteZ = RegWrite & (DestAddr[5:4] == 2'b10);
assign WriteSP = RegWrite & (DestAddr[5:4] == 2'b11);

wire isSource = (~SrcDiscard)&(~SrcStall)&(SrcAddr[7:6] == 2'b11);
assign CountSP = (DestCountSP | (fetching & isSource & SrcAddr[5] & SrcAddr[4] & SrcAddr[1] & SrcAddr[0]));
assign SPup = ~SecondIndex[0];

assign busy = isSource & SrcSquareBrac & (~fetching);

//осталось с ним сообразить. Когда приходит SrcAddr[7:6] == 2'b11 и SrcDiscard = 0, то невзирая на SrcStall, мы уже начали выборку! 
//и значит, когда нам "позволят" работать, уже получим результат!
//но ещё, когда SrcStall=0 и fetching=1, он должен сброситься опять в ноль,
//чтобы идущая сразу следом команда не могла ошибочно выдать результат СРАЗУ
always @(posedge clk)
	fetching <= (~SrcDiscard)&(SrcAddr[7:6] == 2'b11)&SrcSquareBrac&((~fetching)|SrcStall);
													
endmodule


Ну и соединяем всё это вместе:



Весь проект в целом синтезируется в 1723 ЛЭ, фиттер сработал очень быстро, но всё равно выдал предельную частоту 24,63 МГц, и "завалено" ровно 20 комбинаторных путей. Сильно меньше, чем 500 с лишним в прошлый раз, но всё ж.

Надо будет ещё "поднажать", но уже завтра, а ещё убедиться, что ничего не поломали.
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

  • О вытягивании себя из болота по методу Мюнхгаузена

    Всё готовлюсь к встрече с представителями РКК Энергия, нужно убедить их в моём способе определения положения ВидеоИзмерителя Параметров Сближения на…

  • Ремонт лыжных мостиков

    Вернулся с сегодняшнего субботника. Очень продуктивно: отремонтировали все ТРИ мостика! Правда, для этого надо было разделиться, благо народу…

  • Гетто-байк

    В субботу во время Великой Октябрьской резни бензопилой умудрился петуха сломать в велосипеде. По счастью, уже на следующий день удалось купить…

  • А всё-таки есть польза от ковариаций

    Вчера опробовал "сценарий", когда варьируем дальность от 1 метра до 11 метров. Получилось, что грамотное усреднение - это взять с огромными весами…

  • Так есть ли толк в ковариационной матрице?

    Задался этим вопросом применительно к своему прибору чуть более 2 недель назад. Рыл носом землю с попеременным успехом ( раз, два, три, четыре),…

  • Big Data, чтоб их ... (4)

    Наконец-то стряхнул пыль с компьютерной модели сближения, добавил в неё код, чтобы мы могли определить интересующие нас точки, и выписать…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 1 comment