nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Обнаружение точек "вживую" - глубоко в отладке

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

Что радует, достаточно взять лишь "вычислительное ядро" QuatCore, без периферии - и уже многое понять. Все команды, завязанные на периферию (IN, OUT, SIO, ACQ, GPUH, GPUL) при этом исполняются за один такт "вхолостую". Некому останавливать конвейер, пока не появятся данные или пока не отправятся, и если логику работы это не нарушает (а для процедур print и SetClock - НЕ НАРУШАЕТ), то всё в порядке.

И вот она проблема, в самом конце процедуры print:




Приведём листинг процедуры print:

    print proc
98  F380                  [SP++]  Acc
99  FDCD                  SP      X
9A  CDFD                  X       SP
9B  8861      @@start:    ZAcc    RoundZero
9C  83FC                  SUB     [SP]
9D  BC05                  JGE     @@finish    ;увы, теперь из процедуры так просто не выпрыгнешь
9E  00F3                  OUT     [SP++]
9F  B06D                  JMP     @@start
A0  FDCD     @@finish:    SP      X
A1  CDFD                  X       SP
A2  80FF                  Acc     [--SP]
A3  B0FF                  JMP     [--SP]
    print endp


А вызывалась она первый раз с адреса 0x0A:
07  FD31      SP      Stack   ;инициализация стека.
08  104D      SIO     LCD
09  CD4D      X       InitLCD
0A  F3BB      CALL    print


На print перешли правильно, и выдали всю запрошенную строку, вот только вернуться куда надо, на адрес 0B, так и не смогли.

Как видно по листингу, мы в неописуемой наглости использовали указатель стека, чтобы пройтись по строке, временно сохраняя его в регистре X (мы меняли их местами, как описано здесь).

Тем не менее, исходное значение 0x148 мы возвращаем (см листинг памяти в этом месте):

Elem1F:           141  0x8000
Elem1F[1]:        142  ????
Elem1F[2]:        143  ????
Elem1F[3]:        144  ????
Elem1F[4]:        145  ????
Stack:            146  ????
Stack[1]:         147  ????
Stack[2]:         148  ????
Stack[3]:         149  ????
                  14A  ????
                  14B  ????
                  14C  ????


Изначально 0x146, но при вызове print смещается на один (в 0x146 идёт адрес возврата), потом сохраняем аккумулятор в 0x147, вот и выходит 0x148, всё как в аптеке.

И по значению SP, вынесенному в результаты симуляции (внизу) мы тоже видим, что всё правильно присвоилось.

Только вот по строке MemAddr видно, что когда мы пытаемся извлечь из стека сначала сохранённое значение Acc, а потом и адрес возврата - мы вместо 0x147 и 0x146 обращаемся к 0x47 и 0x46, что заканчивается эпик фейлом, мы выпрыгиваем вообще куда-то в "пустырь", и там исполняем нулевые команды, пока не доползём своим ходом до адреса 0, после чего всё начинается заново!

И только сейчас я вспомнил, что [--SP] реализован немного своеобразно: разумеется, приходит сигнал "вычесть единичку" на реверсивный счётчик, в котором и хранится SP. Но это-то будет к следующему такту, т.е [SP--]. Поэтому мы ещё и честно вычитаем единичку с помощью наших сумматоров индексных регистров:


Вот код QuatCoreFirstIndexMux:

module QuatCoreFirstIndexMux (input [15:0] ijk, input [1:0] Base, input [1:0] adr, 
								output [7: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)? 8'b00000000 :		//0
		(adr == 2'b01)? {2'b0, i, 1'b0} : 	//2i
		(adr == 2'b10)? {2'b0, j, 1'b0} : 	//2j
		(~Base[0])?	{1'b0, j, 2'b0}	: 	//4j for X or Z
		Base[1]?	8'b11111111 :		//-1 for SP
				{4'b0, TreugNum};	//TreugNum for Y
endmodule


Укуренный код, и последний раз он редактировался 6 января 2020 года, в 2:56 ночи :) Это многое объясняет!

Хотя, определённая система тут есть. Нам здесь нужны "треугольные числа" Treug[j] = j(j+1)/2, но только первые 6:
0, 1, 3, 6, 10, 15.
(для адресации треугольной матрицы 6х6)

Помнится, я их выписал в бинарном виде:
j     Treug[j]
000       0000
001       0001
010       0011
011       0110
100       1010
101       1111


И понял, что 3-й бит Treug[] можно соединить напрямую со 2-м битом j. А вот для трёх младших битов сколько-нибудь простой закономерности не нашлось - и я выписал их "в лоб".

Интереснее другое: почему здесь 8-битный выход. Самые крупные значения получаются для варианта "4j", ведь сам j может быть от 0 до 31, значит 4j - от 0 до 124, влезает в 7 бит.

Очевидно, чтобы не спутать большие положительные значения вроде 124, и отрицательное "-1", я и ввёл сюда 8-й бит. Это по сути получился ДОПОЛНИТЕЛЬНЫЙ КОД. Определённая логика прослеживается...

Далее, посмотрим модуль QuatCoreMemSecondIndexMux:

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]&Base[1])?	5'b00000 :
					i^j;
							
endmodule


Здесь всё поскромнее: умножать на 2 или 4 ничего не надо, в крайнем случае два регистра объединить через XOR, и 5 бит хватает "за глаза".

Теперь посмотрим на QuatCoreMemIndexAdder, который складывает два результата, полученных ранее:

module QuatCoreMemIndexAdder (input [7:0] First, input [4:0] Second, output [7:0] Q);

	assign Q = First + Second; //will it be kind enough to synthesize this to 7 LE?

/*
	lpm_add_sub adder (	.dataa (First), //full acc width
				.datab ({2'b00, Second}), //this one extended as well
				.result (Q[6:0]),
				.cout (Q[7]));
	defparam
		adder.lpm_direction = "ADD",
		adder.lpm_hint = "ONE_INPUT_IS_CONSTANT=NO,CIN_USED=NO",
		adder.lpm_representation = "UNSIGNED",
		adder.lpm_type = "LPM_ADD_SUB",
		adder.lpm_width = 7;
*/
endmodule


В кои-то веки не пришлось вводить сумматор "в явном виде", и без этого нормально. Очень частая ситуация, когда мы здесь складываем "-1" и "+1", получая в итоге ноль (см. магические квадраты).

Если так подумать, возможна проблема с выражением [X+4j+i] или подобным. Если j=31 (максимально возможное) и i=31, то 4j+i = 155, что уже не влезет в 8-битное ЧИСЛО СО ЗНАКОМ.

Так что впору здесь сделать 9-битный выход, но для этого нужно расширить знак у 8-битного входного числа:

module QuatCoreMemIndexAdder (input [7:0] First, input [4:0] Second, output [8:0] Q);

	assign Q = {First[7],First} + Second;

endmodule


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

И наконец, поглядим, что такое QuatCoreMemMainAdderBanded:

module QuatCoreMemMainAdderBanded (input [RamWidth+1:0] Base, input [7:0] Index, output [RamWidth-1:0] Q);

	parameter RamWidth = 8;

	assign Q = Base[RamWidth-1:0] + Index;
	
endmodule


Собственно, именно здесь ошибка и закралась: мы забыли расширение знака сделать!

Пока у нас размер оперативной памяти был 256 слов или меньше - всё работало правильно, поскольку расширения знака и не требовалось. А на 512 словах (9-битная адресация) и пришла к нам хана. К 0x147 прибавляется 0xFF - и выходит 0x246, но старший бит отбрасывается - и выходит 0x46. Вот и вся проблема...

Напишем как-то так:
module QuatCoreMemMainAdderBanded (input [RamWidth+1:0] Base, input [8:0] Index, output [RamWidth-1:0] Q);

	parameter RamWidth = 9;

	localparam ExtensionBits = RamWidth - 9;
	
	wire [RamWidth-1:0] ExtIndex = (ExtensionBits > 0)? {{ExtensionBits{Index[8]}}, Index} : Index[RamWidth-1:0];
	
	assign Q = Base[RamWidth-1:0] + ExtIndex;
	
endmodule


То есть, когда надо, делаем расширение знака, в противном случае можем вообще старшие биты отрезать.

Снова отсинтезируем QuatCore (процессорное ядро без периферии) и запустим симуляцию:


Фух, работает!

Вот теперь можно попробовать и на "железе":



Ну, уже получше :)

К следующему разу так:


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

Recent Posts from This Journal

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

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

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

    Вчера я чуть поторопился отсинтезировать проект,параметры не поменял: 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 

  • 2 comments