nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Весеннее обострение - 3

Пока меня не трогают, надо продолжить возню с АЛУ (раз, два). Доделываем приятную часть, соединяем воедино обновлённые модули:



Получается сколько-нибудь симметрично: три сигнала соединяются по "ИЛИ" для формирования DestStall (от Mem, т.к на выборку из памяти требуется дополнительный такт, и от АЛУ, если мы запрашиваем результат работы, а он ещё не готов, и "внешний вход"), и также три сигнала - для формирования SrcStall (от АЛУ, когда мы даём ему новое задание, но ещё не завершилось старое; от PC, счётчика инструкций, если выполняем условный переход JL/JGE/JO/JNO и ждём, пока АЛУ закончит работу, и "внешний вход").

По крайней мере, всё это дело синтезируется. С ужасом обнаружил, что сейчас ядро QuatCore занимает 698 ЛЭ, из них 162 ЛЭ занимает мультиплексор шины данных. Ну да, у него разрядность 16 бит, и за время разработки он расширился аж до 7 входов:
- IMM (непосредственные значения),
- MEM (регистры X,Y,Z,SP или значения из памяти),
- ALU (аккумулятор либо регистр C),
- PC (адрес возврата либо регистры i,j,k,Inv),
- IN (вход от SPI или UART),
- GPU (результаты работы видеообработчика),
- SRAM (внешняя статическая память).

(кстати, тут он считает что все входы 16-битные, хотя на деле мы в SRAM и IN подаём только 8 бит. Но только "с обвязкой" он срежет лишнее)

И ещё, кстати, 93 ЛЭ занимает ImmTable. Есть идея объединить IMM с памятью, т.е вместо специальной таблицы просто воспринимать SrcAddr = 0..127 как доступ к соотв. адресам в памяти, а туда уже занести константы. Но это потом, сейчас "работает - не лезь!".

И остаётся всего-то ничего: переделать модуль ALUcontrol, который всем и заправляет в АЛУ!


Начинаем прямо с первой строки:
wire isOurAddr = (DestAddr[7:5] == 3'b100)&(~DestStall)&(~rBusy);


Этот сигнал, isOurAddr, должен указывать, что нам дают новое задание. rBusy - это наш "костыль", чтобы если SrcAddr также относится к АЛУ (мы запрашиваем результат работы) и возникла задержка на лишний такт, конечный автомат не начинал повторно выполнять то же самое. Сейчас это "официально" прогоняется через госуслуги механизм DestStallReq. Так что rBusy можно с чистой совестью отсюда убрать:

wire isOurAddr = (DestAddr[7:5] == 3'b100)&(~DestStall);


И сразу надо разобраться с цепями busy:
wire MainBusy = isOurAddr & (DestAddr[4:2] != 3'b0_10)&(~isAdd)&(~finished);
wire AuxBusy = (SrcAddr[7:4] == 4'b1000) & (isAdd | finished);
assign Busy = MainBusy | AuxBusy;


Идея была, что как только нам дают задание (т.е isOurAddr = 1), мы всё останавливаем (выдаём busy = 1) до тех пор, пока не останется последний такт работы, т.е состояние sAdd либо последний такт умножения (когда счётчик досчитал и выдал finished = 1). Исключение составляют команды, выполняющиеся за 1 такт, это "C" (0x8A..8B, занести значение в регистр С), ZAcc ( 0x88, инициализировать аккумулятор одним из "хитрых" значений) и NOP (0x89). isOurAddr = 1 означает, что адрес начинается с 100, а здесь проверяются ещё 3 бита, т.е требуем 1000_10xx - это и есть 0x88..0x8B.

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

Давайте сразу же разберёмся с формированием нового Busy (для условных переходов JO/JNO и для выхода АЛУ), SBusy (Sign-Busy для условных переходов JL/JGE) и SrcStallReq (остановка процессора, если нам дали новое задание, когда мы ещё не доделали старое).

Как ни странно, SrcStallReq очень похож на нынешний busy. Первым необходимым условием является очередная "наша" команда, т.е isOurAddr = 1. При этом мы уже должны работать, т.е состояние не должно равняться sIdle, даже если новая команда всего на 1 такт работы. Но подождать окончания предыдущей всё равно необходимо! То есть, в первом приближении выходит так:

assign SrcStallReq = isOurAddr & (~isIdle);


Но мы же хотим ещё один такт сэкономить, заблаговременно начав загрузку в регистр B, зная, что он уже "освободился". Это надо делать либо в состоянии sAdd, либо на последнем такте умножения, когда finished = 1.

Отдельно нужно рассмотреть самые короткие команды. ZAcc напрямую помещает значение в аккумулятор и может нам одним махом "сбросить" всё что мы так долго и упорно считали! Рассмотрим такой код:

FMA   [X+k]
kLOOP @@loop
[SP]  Acc
ZAcc  RoundZero


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

Когда поступает команда FMA на DestAddr, на SrcAddr поступает константа @@loop (адрес, куда прыгать). Выполняется эта строка всего за один такт (если АЛУ был свободен) - его озадачили и двигаемся дальше.

А вот следующей будут выполняться команды kLOOP и Acc. Допустим, условный переход не происходит. Тогда мы всерьёз и надолго застрянем на команде Acc, поскольку она будет ждать, когда же завершится работа АЛУ, чтобы выдать правильный результат. И только после этого процессор перейдёт на следующую команду, где занесёт результат на стек, и лишь потом последует ZAcc.

Мораль такова: если на занятый АЛУ поступает команда ZAcc, значит, предыдущая работа действительно "ушла впустую", и ничего страшного, если ZAcc обнулит аккумулятор, ДАЖЕ НЕ ДОЖИДАЯСЬ ОКОНЧАНИЯ! Поэтому не будем вносить специальной блокировки на этот случай.

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

Наконец, команда NOP, которая вообще ничего не делает, имеет полное право выполниться "параллельно" с завершающейся предыдущей командой :)

Так что мы с чистой совестью можем записать так:
assign SrcStallReq = isOurAddr & (~isIdle) & (~isAdd) & (~finished);


По-моему, даже чуть проще, чем было, что не может не радовать.

Далее, подумаем о сигнале Busy. Воспримет его либо выход АЛУ (если мы запрашиваем результаты), либо счётчик инструкций при выполнении условного перехода. В обеих ситуациях, как мы заметили ранее, новая команда для АЛУ не может поступить на DestAddr, поскольку либо там будет условный переход, либо СТАРАЯ команда для АЛУ, т.к командой с SrcAddr нас остановили. Так что с чистой совестью можно не задумываться о "наложении" команд. И сразу всё становится просто: надо лишь убедиться, что мы не сидим в состоянии sIdle:
assign Busy = ~isIdle;

Люблю такой код :)

И давайте пока сделаем небольшой костыль с SBusy:
assign SBusy = Busy;

Так наше ядро будет работать корректно, разве что иногда дольше чем надо. Не уверен, что у меня в коде программ есть хоть одно место, где более умное формирование SBusy принесёт пользу. В принципе, тут ничего сложного, однако нужно разобраться с запоминанием нашего "задания".

Теперь давайте разберёмся с конечным автоматом, т.е регистром state.

Так оно выглядит сейчас:
always @(posedge clk)
	State <=isIdle? 		(Busy? ((DestAddr[3:2] == 2'b11)? sDiv2 : sFirstDiv) : sIdle):
		isDiv2?			sFirstDiv:
		isFirstDiv?	 	(isLongCommand? sMult : sAdd):
		(isAdd | finished)?	sIdle : State;


Мы сидим в состоянии sIdle, пока не сформируется СТАРЫЙ сигнал Busy, т.е поступила команда, занимающая более такта для выполнения. Если так, мы прыгаем либо на sDiv2 (дополнительный шаг, чтобы поделить входное значение на 2), либо сразу на sFirstDiv (первый сдвиг вправо в регистре B, на котором сформируется знаковое/беззнаковое число и при необходимости поменяется его знак, см. проблемы с UFMS).

То, что используется DestAddr напрямую - не страшно, т.к команда "только поступила". Сигнал Busy нужно заменить на isOurAddr & (DestAddr[4:2] != 3'b0_10). Логично: именно это упоминание и задаст этим командам длительность в 1 такт (т.е они вообще не меняют состояние).

Итак, с переправленной первой строкой:
always @(posedge clk)
	State <=isIdle? 		(CmdMoreThan1clk? ((DestAddr[3:2] == 2'b11)? sDiv2 : sFirstDiv) : sIdle):
		isDiv2?			sFirstDiv:
		isFirstDiv? 		(isLongCommand? sMult : sAdd):
		(isAdd | finished)?	sIdle : State;


Условие перехода из sDiv2 не поменялось. Собственно, никакого условия: через такт непременно переходим в sFirstDiv.

Условие перехода из sFirstDiv: в зависимости от того, "длинная" ли команда - либо на sAdd (сложение), либо на sMult (умножение). Да, так оно и останется, только вот isLongCommand надо будет переопределить, об этом чуть ниже.

И наконец, из sAdd или из sMult на последнем такте (когда пришло finished = 1) мы возвращались в sIdle. Сейчас, чтобы сэкономить один такт, мы должны прямо из этого места запускать выполнение следующей команды. Практически повторим первую строку (условие по sIdle), итого выйдет так:

wire CmdMoreThan1clk = isOurAddr & (DestAddr[4:2] != 3'b0_10);

always @(posedge clk)
	State <=isIdle? 		(CmdMoreThan1clk? ((DestAddr[3:2] == 2'b11)? sDiv2 : sFirstDiv) : sIdle):		
		isDiv2?			sFirstDiv:
		isFirstDiv? 		(isLongCommand? sMult : sAdd):
		(isAdd | finished)?	(CmdMoreThan1clk? ((DestAddr[3:2] == 2'b11)? sDiv2 : sFirstDiv): sIdle) : State;


Давайте теперь разберёмся с комбинаторной логикой (и не только) в начале модуля, со сколько-нибудь говорящими именами:
wire isLongCommand = DestAddr[4];
wire [1:0] PlusMinusMode = DestAddr[1:0];

wire OpSign = PlusMinusMode[0] & (PlusMinusMode[1] | PM); //0 is '+', 1 is '-'
wire isSignedC = (DestAddr[3] == DestAddr[2]);

reg SeniorDataBit = 1'b0;
always @(posedge clk) if (isIdle)
	SeniorDataBit <= D[15];

wire FirstDivSign = OpSign ^ ((isLongCommand & isSignedC & (isFirstDiv | MulFirst)) | (SeniorDataBit & (~isLongCommand) & (~DestAddr[3]) & DestAddr[2]));


isLongCommand понадобится, чтобы выбрать, в какое состояние переключиться, sAdd (сложение) или sMult (умножение), а также для выбора знака регистра B в состоянии sFirstDiv. Также он повлияет на режим работы аккумулятора в состояниях sDiv2 или sFirstDiv.

Важно то, что это значение никогда не используется в состоянии sIdle, то есть вполне допустимо задержать его на один такт относительно выдачи задания. Так мы и сделаем - сохраним isLongCommand в регистр, потому как уже на следующем такте DestAddr может смениться - процессор продолжит другие дела, пока мы тут копаемся!

Но сразу же надо понять, когда именно нужно защёлкивать данные в регистр. Очевидно, если мы в состоянии sIdle и выбрана команда (т.е isOurAddr = 1). Но если идёт последний такт работы АЛУ, то нам не стоит дожидаться возвращения в sIdle - мы уже можем "принять" новую команду и начать её исполнение. Введём соответствующий сигнал ("провод"):

wire LatchNewCmd = isOurAddr & (isIdle | isAdd | finished);


дешево и сердито :)

И теперь зададим isLongCommand как регистр:
reg isLongCommand;
always @(posedge clk) if (LatchNewCmd)
	isLongCommand <= DestAddr[4];


Далее, у нас безумная возня со знаками. PlusMinusMode опять же, просто являлся удобным именем для двух младших разрядов адреса, которые определяли, как АЛУ должна поступать со старым значением в аккумуляторе и со знаком нового "операнда":
00: аккумулятор очищается, новое значение поступает со знаком "+" (команды Acc, DIV2, ABS, MUL и пр),
01: к аккумулятору прибавляется/вычитается новое значение, в зависимости от регистра Inv и, возможно, регистров i,j (команды PM, FMPM и пр)
10: к аккумулятору ПРИБАВЛЯЕТСЯ новое значение (команды ADD, FMA и пр),
11: из аккумулятора ВЫЧИТАЕТСЯ новое значение (команды SUB, FMS и пр).

Давайте сразу посмотрим код, управляющий работой аккумулятора:
assign AccMode =isIdle? 		((isOurAddr & (PlusMinusMode == 2'b00))? {1'b1, D[1:0]} : 3'b010) :
		(isFirstDiv | isDiv2)?	{isLongCommand & (PlusMinusMode == 2'b00), 1'b1, (PlusMinusMode == 2'b00)}:
		isAdd?			3'b000:
					{1'b0, ~Csenior, 1'b0};


И ещё приведём табличку, что означают 8 режимов аккумулятора:
//mode = 000: load from D
//mode = 001: FORBIDDEN
//mode = 010: idle,
//mode = 011: clear,
//mode = 100: -3/2   (101000...)
//mode = 101: set -1 or 1 (doesn't matter)  (x10000...)
//mode = 110: set 3/2 (01100...)
//mode = 111: set 1/2lsb (00000...1)


Видно, что в режиме sIdle мы не проводим полной проверки команды ZAcc (0x88), проверяем лишь PlusMinusMode = 00. Если это была команда ZAcc - мы уже её выполним, а если любая другая - на следующем такте аккумулятор всё равно обнулится, и всем пофиг, что мы сделали на первом такте :) Если PlusMinusMode не равен 00, то храним текущее значение, т.е посылаем 010 = idle.

Просто переправим PlusMinusMode на DestAddr[1:0]:
assign AccMode =isIdle? 		((isOurAddr & (DestAddr[1:0] == 2'b00))? {1'b1, D[1:0]} : 3'b010) :
		(isFirstDiv | isDiv2)?	{isLongCommand & (PlusMinusMode == 2'b00), 1'b1, (PlusMinusMode == 2'b00)}:
		isAdd?			3'b000:
					{1'b0, ~Csenior, 1'b0};


Следующая строка (режим на sDiv2 / sFirstDiv) наиболее загадочна. Если PlusMinusMode не равен 00, то будет выбран режим idle (010), что логично - вычисления ещё не начались, а предыдущее значение нам необходимо, мы будем к нему что-то прибавлять или отнимать.

Если PlusMinusMode = 00, то на "длинных" командах мы получаем режим 111, что означает помещение 1/2 младшего разряда в аккумулятор. Таким образом, когда мы умножим два 16-битных значения и заберём из аккумулятора 16-бит результата, они уже будут округлены как надо. Не факт, что это правильное решение: если мы используем Acc как вход для АЛУ, то сработает байпас, который скоммутирует нам и младшие биты, и тогда округление нам было во вред. По-моему, когда я это дело придумывал, о байпасе ещё не задумывался. Может, в итоге уберём это дело. Ну а на "коротких" командах (сложение, деление на 2, модуль числа) получаем режим 011, это полное обнуление аккумулятора, что логично.

Здесь нам нужен именно PlusMinusMode, который мы также превратим в регистр, потому как DestAddr к тому времени может поменяться.

Третья строка очень проста: во время сложения мы заносим в аккумулятор результат из сумматора. Тут мы могли бы рассмотреть команду ZAcc, пришедшую сразу после команды сложения. Но наверное, лучше объявить такую комбинацию как Hazard и заставить компилятор давать предупреждение. Это очень странное поведение - что-то усиленно считать и, не сохранив никуда результаты, сразу их сбросить.

Наконец, на четвёртой строке реализуется умножение: если очередной разряд регистра C (один из множителей) - единичка, прибавляем значение из регистра B, иначе - оставляем неизменным (т.е прибавляем умноженное на 0). Та же история: могли бы рассмотреть ZAcc пришедший на последнем такте, но пусть он лучше проигнорируется, а компилятор предупредит нас об этом.

Ещё одно использование PlusMinusMode - в "проводе" OpSign:
wire OpSign = PlusMinusMode[0] & (PlusMinusMode[1] | PM);


Здесь мы "окончательно" разбираемся со знаком: так всё-таки плюс или минус? Сюда же подтягиваем и провод PM ведущий из регистра Inv.

Далее, этот OpSign используется в монструозном выражении:
wire FirstDivSign = OpSign ^ ((isLongCommand & isSignedC & (isFirstDiv | MulFirst)) | (SeniorDataBit & (~isLongCommand) & (~DestAddr[3]) & DestAddr[2]));


И БОЛЬШЕ НИГДЕ. А само это выражение, FirstDivSign, напрямую подаётся на вход cin (Carry In) сумматора, но реально на что-то повлияет только когда аккумулятор в режиме 000 (защёлкнуть значение с выхода сумматора), а такое происходит только в состояниях sAdd и sMult. Далее, это выражение используется при задании режима регистра B в состоянии sFirstDiv - И ВСЁ.

Предлагаем ввести регистр DoClear вместо PlusMinusMode, и сделать регистром FirstDivSign - и этого нам хватит!

Как-то так:
reg DoClear;
always @(posedge clk) if (LatchNewCmd)
	DoClear <= (DestAddr[1:0] == 2'b00);
	
wire OpSign = DestAddr[0] & (DestAddr[1] | PM); //0 is '+', 1 is '-'
wire isSignedC = (DestAddr[3] == DestAddr[2]);

reg FirstDivSign;
always @(posedge clk) if (LatchNewCmd)
	FirstDivSign <= OpSign ^ ((DestAddr[4] & isSignedC & (isFirstDiv | MulFirst)) | (SeniorDataBit & (~DestAddr[4]) & (~DestAddr[3]) & DestAddr[2]));


Ну и при установке режима аккумулятора меняем (PlusMinusMode == 2'b00) на DoClear:

assign AccMode =isIdle? 		((isOurAddr & (DestAddr[1:0] == 2'b00))? {1'b1, D[1:0]} : 3'b010) : 
		(isFirstDiv | isDiv2)?	{isLongCommand & DoClear, 1'b1, DoClear}:
		isAdd?			3'b000:
					{1'b0, ~Csenior, 1'b0};


Был у нас вот такой кусочек:
reg SeniorDataBit = 1'b0;
always @(posedge clk) if (isIdle)
	SeniorDataBit <= D[15];


Но этот самый SeniorDataBit использовался только в выражении FirstDivSign, поэтому туда необходимо защёлкнуть просто D[15], а регистр этот и не нужен!

Рассмотрим теперь регистр B:
assign BSigned = 	(DestAddr[3:2] != 2'b10) | (~isFirstDiv); //this row equals fully unsigned operation
//assign BSigned = (DestAddr[3:2] != 2'b10) & (isLongCommand | (~DestAddr[1])); //making one and only operation DIV2S into UDIV2S.
							
assign Bmode = 	isIdle? 	{1'b1, Bypass}:
		isFirstDiv?	{1'b0, FirstDivSign} :
				{1'b0, isSignedC & MulFirst};


Верхнее выражение, BSigned, выбирает режим сдвига регистра B. Если BSigned=0, то в старшем разряде при сдвиге вправо появляются нули (логический сдвиг, работает как деление на 2 для беззнаковых чисел). Если BSigned=1, то старший разряд остаётся неизменным (арифметический сдвиг, работает как деление на 2 для знаковых чисел).

Здесь единственное место, где BSigned = 0 - это в состоянии sFirstDiv, на одной из команд ПОЛНОСТЬЮ БЕЗЗНАКОВОГО УМНОЖЕНИЯ. См. проблемы с UFMS, там мы мучительно выводили данную логику. Всё хорошо, только напрямую обращаться к DestAddr нельзя, надо ввести ещё один регистр, типа rBisSigned.

И теперь разберёмся с режимами регистра B. Их у него 4:

//Bmode   =	00 - shift right
//		01 - shift right and invert
//		10 - load 16 bit
//		11 - load AccWidth bit (bypass)


Первая строчка кода говорит: в состоянии sIdle мы ВСЕГДА загружаем что-нибудь в регистр B (такая у него непостоянная натура!), и в отличие от внешнего сигнала Bypass в нижние биты размещаем либо нули, либо значения с выхода аккумулятора. Сейчас нам нужно это делать не только в sIdle, но и в sAdd, и на последнем такте sMult.

На второй строчке говорится: в состоянии sFirstDiv мы осуществляем сдвиг вправо либо с инверсией, либо нет, а делать ли инверсию - определяется очень сложным выражением FirstDivSign. все подробности в проблемах с UFMS. И к этой строке у нас претензий нет, всё верно.

На третьей строке - всё оставшееся время, то есть режим sAdd (но это было неважно, sAdd мы отберём), sMult и sFirstDiv. И здесь знак меняется только в одном случае - если у нас выполняется ПЕРВЫЙ такт умножения sMult, и при этом регистр C мы интерпретируем как знаковый. Вся хитрость - в том, чтобы воспринять старший бит регистра C как "-1", для чего мы "обращаем" знак sFirstDivSign, а потом ещё раз обращаем знак после первого такта умножения.

Здесь тоже нас всё устраивает, только выражение Csigned сейчас является комбинаторным (напрямую через DestAddr), а нужно его поместить в регистр, т.к DestAddr может поменяться.

Итого, выражение для BMode перепишется так:
assign Bmode = 	(isIdle | isAdd | finished)? 	{1'b1, Bypass}:
		isFirstDiv?			{1'b0, FirstDivSign} :
						{1'b0, isSignedC & MulFirst};


И не забыть о регистре isSignedC.

Ещё немного осталось, буквально 2 строки...

Рассмотрим регистр C

У него 3 режима работы, задаваемые двумя битами:
//Cmode = 	0x - idle
//		10 - shift
//		11 - load


И так задаются режимы:
assign Cmode[1] = isIdle? isOurAddr & DestAddr[3] & (((~DestAddr[4])&(~DestAddr[2])&DestAddr[1])|(DestAddr[4]&DestAddr[2])) : isMult; 
									
assign Cmode[0] = ~isMult;


Как видно, в состоянии sIdle режим задаётся особенно стрёмным выражением, но выбор всегда будет между 00 (idle, оставить регистр "как есть") и 10 (load, т.е загрузить новое значение с шины данных).

В принципе, мы здесь просто делаем декодирование адреса. Регистр C задействуется либо в команде "C", либо в командах SQR (возведение в квадрат), где одно и то же значение заносится одновременно и в C, и в B.

Собственно, нас устраивают либо команды вида 1000_101x, то есть 0x8A и 0x8B, это и есть команда "C". Либо команды вида 1001_11xx, то есть 4 команды от 0x9C по 0x9F, это как раз разновидности SQR.

Во всех состояниях, кроме sIdle и sMult, имеем режим 01, т.е idle - регистр остаётся "как есть". И наконец, в состоянии sMult получается режим 10 - это циклический сдвиг на единичку. За 16 тактов в старшем разряде успевают побывать все биты, и затем регистр возвращает своё значение "как было".

В общем-то, всё хорошо, только загружать новое значение в регистр нужно не только в состоянии sIdle, но и в sAdd и последнем такте sMult, если у нас поступила новая команда:

assign Cmode[1] = (isIdle | isAdd | finished)? isOurAddr & DestAddr[3] & (((~DestAddr[4])&(~DestAddr[2])&DestAddr[1])|(DestAddr[4]&DestAddr[2])) : isMult;								
assign Cmode[0] = ~isMult;



И последнее - управление флагом знака:
assign Sena = isOurAddr & DestAddr[0] & DestAddr[1] | isMult;


Знак всегда присваивается в операциях умножения, а также в командах SUB, ABSS (вычесть из аккумулятора модуль числа), DIV2S и неиспользуемую команду 0x8B. Мы её записали как "C", но похоже это не совсем так: кроме записи в регистр C, она ещё выставит знак по текущему значению в аккумуляторе :)

Претензия уже классическая: нельзя напрямую брать DestAddr и isOurAddr, т.к пока АЛУ что-то считает, остальной процессор может заниматься другими вещами! Так что условие выполнения необходимо "защёлкнуть". Так что введём ещё один регистр, "AllowS". Но если мы будем точно также защёлкивать его при поступлении "нашей" команды, то по окончании выполнения команды мы можем продолжить защёлкивать всё новые и новые значения в флаг S. Пожалуй, заставлю его выставлять только на этапе sAdd - так проще всего.

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


Как будто бы мы всё переделали. Новый модуль успешно синтезируется в 43 ЛЭ (старый синтезировался в 42), что не так уж плохо. Как бы отдать 1 ЛЭ за ускорение местами на 33% меня вполне устраивает. Наверняка, можно и ещё немножко "вылизать", но не будем жадничать. Мне кажется, и быстродействие может возрасти - длинные комбинаторные цепочки мы "разбили" регистрами.

Осталось за малым - отладить это дело, и посмотреть, стоила ли игра свеч.
Tags: ПЛИС, работа, странные девайсы
Subscribe

  • Нахождение двух самых отдалённых точек

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

  • Слишком общительный счётчик

    Вчера я чуть поторопился отсинтезировать проект,параметры не поменял: RomWidth = 8 вместо 7, RamWidth = 9 вместо 8, и ещё EnableByteAccess=1, чтобы…

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

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

  • Огари разговаривают

    Сегодня по пути на работу встретил огарей прямо в Лосином острове, на берегу Яузы. Эти были на удивление бесстрашны, занимались своими делами, не…

  • Ковыряемся с сантехникой

    Наконец-то закрыл сколько-нибудь пристойно трубы, подводящие к смесителю, в квартире в Москве: А в воскресенье побывал на даче, там очередная…

  • Мартовское велосипедное

    Продолжаю кататься на работу и с работы на велосипеде, а также в РКК Энергию и на дачу. Хотя на две недели случился перерыв, очередная поломка,…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments