nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

QuatCoreMem готов (но не тестировался)

Всё оказалось не так страшно. В сравнении с АЛУ, этот модуль - совсем простой! Занимает 145 ЛЭ - хотелось бы чуточку поменьше, но что уж поделать.




Первым делом, выпишем таблицу адресов:



Мы ПОЧТИ достигли ортогональности: адрес, по которому мы будем обращаться в оперативную память, составляется из 3 частей. Это базовый регистр, X, Y, Z или SP, и ещё два слагаемых.

Из 8 бит адреса DestAddr / SrcAddr, 2 бита отводится на выбор модуля QuatCoreMem (а именно, старшие биты 11), 2 бита - на выбор базового регистра, 2 бита - на выбор первого слагаемого и ещё 2 - на выбор второго.

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

Как видно, меньше всего мы полюбили адрес 2i+i^k и 2i+i^j, т.к смысла в них действительно немного. Для стека мы выкинули ещё больше "хитрых" адресов, заменив оба i^k и 4j на нули, но с дополнительными инструкциями по инкременту-декременту.

Что интересно, программист может сам выбрать, в какую сторону двигаться стеку, поскольку команды [++SP] и [SP--] доступны как в поле Dest, так и в поле Src. Небольшая разница всё-таки есть. Сейчас я использовал локальные переменные ([SP], [SP+1], [SP+2]) с той стороны, куда растёт стек, т.е если бы где-то посередине я бы вызвал процедуру, то они бы потёрлись. Если нужно, чтобы локальные переменные сохранялись, то лучше стеку расти "вниз", а при заходе в процедуру, ручками передвинуть SP на столько позиций вниз, сколько у нас локальных переменных. Теперь от этой самой нижней позиции мы будем обращаться к ним через те же самые команды ([SP], [SP+1], [SP+2]), а при ещё одном вызове процедуры указатель сдвинется вниз, а затем вернётся на место.

Рассмотрим, как всё это безобразие реализовано. В левой части стоит декодер адресов QuatCoreMemDecoder, который раздаёт управляющие воздействия на все остальные части схемы, вот его код:

module QuatCoreMemDecoder (input [7:0] DestAddr, input [7:0] SrcAddr,
			output SquareBrac, 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 MemWrite	);
							
wire isDest = (DestAddr[7:6] == 2'b11);
assign BaseAddr = 	isDest? DestAddr[5:4] : SrcAddr[5:4];
assign FirstIndex = 	isDest? DestAddr[1:0] : SrcAddr[1:0];
assign SecondIndex = 	isDest? DestAddr[3:2] : SrcAddr[3:2];

assign SquareBrac = isDest? (DestAddr[3:0] != 4'b1101) : (SrcAddr[3:0] != 4'b1101);

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

wire isSource = (SrcAddr[7:6] == 2'b11);
assign CountSP = (isDest | isSource) & (BaseAddr == 2'b11) & (FirstIndex == 2'b11);
assign SPup = ~SecondIndex[0];

assign MemWrite = isDest & SquareBrac;
											
endmodule

Мы придерживаемся принципа: чтобы выдать что-то на выход БЕЗ ИЗМЕНЕНИЯ ВНУТРЕННЕГО СОСТОЯНИЯ, мы можем проверять входные адреса "спустя рукава". Не проверять, что адрес наш, потому что если это не так - мультиплексор, объединяющий выходы разных модулей, тупо нас проигнорирует.

А вот запись в память или в регистры - это серьёзно, нужно все биты проверить. Поэтому мы формируем сигнал isDest, указывающий, что мы хотим записать что-то в память (или в базовые регистры). Если так, мы напрочь игнорируем SrcAddr, т.е Dest у нас в приоритете (именно поэтому - эти 2 бита мы ОБЯЗАНЫ проверить, а старшие биты SrcAddr за нас проверит мультиплексор). Да, пришлось пожертвовать возможностью прямых пересылок из памяти в память, но мы не так уж часто этим пользовались - можем использовать регистр C в качестве перевалочного пункта.

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

Далее следуют сигналы разрешения записи в базовые регистры. Затем - сигнал "счёта" указателя стека, и очень "лениво" - выбор направления счёта, т.е инкремент и декремент. Ведь если разрешение на счёт не получено, то направление не играет роли, пущай оно "болтается", когда подаются "чужие" команды - нам плевать.

И наконец, сигнал на запись в оперативную память.

Чистая комбинаторная логика, громоздкая слегка, но всё на ладони.

Далее, идёт модуль QuatCoreMemReg, содержащий наши 4 базовых регистра. Вот его код:

module QuatCoreMemReg (input clk, input [15:0] D, input WriteX, input WriteY, input WriteZ, input WriteSP, input CountSP, input SPup,
					   output reg [RamWidth-1:0] X = 1'b0, output reg [RamWidth-1:0] Y = 1'b0, output reg [RamWidth-1:0] Z=1'b0, output [RamWidth-1:0] SP);

parameter RamWidth = 8;

always @(posedge clk) begin
	if (WriteX)
		X <= D[RamWidth-1:0];
	if (WriteY)
		Y <= D[RamWidth-1:0];
	if (WriteZ)
		Z <= D[RamWidth-1:0];
end

lpm_counter SPreg (	.clock (clk),
			.data (D[RamWidth-1:0]),
			.sload (WriteSP),
			.cnt_en (CountSP),
			.updown(SPup),
			.Q (SP));
defparam
	SPreg.lpm_type = "LPM_COUNTER",
	SPreg.lpm_width = RamWidth;

endmodule


Тут вообще всё элементарно - 3 регистра совсем "защёлки" (очень надеюсь, что хоть немножко комбинаторной логики уходит сюда), а четвертый (SP) - это реверсивный счётчик с входом синхронной загрузки.

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

module QuatCoreMemBaseMux (input [RamWidth-1:0] X, input [RamWidth-1:0] Y, input [RamWidth-1:0] Z, input [RamWidth-1:0] SP,
			input [1:0] adr, output [RamWidth-1:0] Q);

parameter RamWidth = 8;
							
assign Q = 	(adr == 2'b00)? X :
		(adr == 2'b01)? Y :
		(adr == 2'b10)? Z :
				SP;
							
endmodule


Ничего сложного.

Мультиплексор, выбирающий первое "индексное" слагаемое, чуть сложнее, поскольку именно в нём реализована функция Treug(j) = j(j+1)/2:

module QuatCoreFirstIndexMux (input [15:0] ijk, input [1:0] Base, input [1:0] adr, 
								output [6:0] Q);
								
wire [4:0] i = ijk[9:5];
wire [4:0] j = ijk[4:0];
wire [2:0] smj = j[2:0]; //for TreugNum
								
wire [3:0] TreugNum;
assign TreugNum[3] = smj[2];
assign TreugNum[2:0] = 	(smj == 3'd0)? 	3'd0 :
			(smj == 3'd1)? 	3'd1 :
			(smj == 3'd2)? 	3'd3 :
			(smj == 3'd3)? 	3'd6 :
			(smj == 3'd4)? 	3'd2 :
			(smj == 3'd5)? 	3'd7 :
					3'bxxx;
										
assign Q = 	(adr == 2'b00)? 7'b0000000 :		//0
		(adr == 2'b01)? {1'b0, i, 1'b0} : 	//2i
		(adr == 2'b10)? {1'b0, j, 1'b0} : 	//2j
		(~Base[0])?	{j, 2'b0}		: 	//4j for X or Z
		Base[1]?	7'b0000000 :
				{3'b0, TreugNum};
endmodule								


Как видно, реализована она таблично, в 3 логических элемента! Потому что, как оказалось, четвертый бит результата совпадает с третьим битом аргумента, по кр. мере для нужных нам 6 значений. Матриц крупнее 6х6 нам пока не встретится.
Регистры i,j,k к нам "прибывают" из модуля QuatCorePC, отдельной шиной.

Следующий мультиплексор, для второго "индекса", попроще:
module QuatCoreMemSecondIndexMux (input [15:0] ijk, input [1:0] Base, input [1:0] adr, output [4:0] Q);

wire [4:0] i = ijk[9:5];
wire [4:0] j = ijk[4:0];
wire [4:0] k = ijk[14:10];

assign Q = 	(adr == 2'b00)? 5'b00001 :
		(adr == 2'b01)? i :
		(adr == 2'b10)? k :
		(~Base[0])?	i^k:
		Base[1]?	5'b00000 :
				i^j;
endmodule


Затем стоит два сумматора. Единственная причина делать свои верилоговские модули взамен lpm_add_sub - чтобы подгадать с шириной входов и выходов. Скажем, 7-битное и 5-битное число вместе дают 8-битное.

В общем, как-то так.
Tags: ПЛИС, математика, работа, странные девайсы
Subscribe

  • Великая Октябрьская резня бензопилой

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

  • Очередная несуразность в единицах измерения

    Когда-то я написал программу PhysUnitCalc - калькулятор, умеющий работать с размерностями. Мне казалось, что я наступил уже на все грабли, которые…

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

    "В предыдущих сериях": мой прибор выдаёт 6 значений: 3 координаты и 3 угла, т.е все 6 степеней свободы твёрдого тела. Причём ошибки измерения этих 6…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 2 comments