nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

"МКО (Mil-Std1553) через UART", часть 3

Давайте проверим хотя бы написанный код, для чего нарисуем "тестовую схему" в квартусе:



И ещё приведём код основного модуля целиком:


module MilStdRemoteTerminalController (input clk,
		//интерфейс с приёмником МКО
		input [15:0] D, input RXisData, input DataValid,
		//интерфейс с передатчиком МКО
		output [15:0] Q, output TXisData, output start, input TxBusy,
		//интерфейс с оперативной памятью (малого объёма, около 1000 слов)
		output [MemWidth-1:0] Addr, input [15:0] MemIn, input MemReady, output MemWrReq, output MemRdReq,
		//интерфейс с часами реал времени
		output sync, input [15:0] TimeStamp,
		//интерфейс с адресной заглушкой
		input [4:0] OurAddr, input AddrParity);
		
parameter MemWidth = 9;
		
reg OurAddrOK = 1'b0;
always @(posedge clk)
	OurAddrOK <= (OurAddr != 5'b11111) & (^OurAddr^AddrParity);
	
wire [4:0] curWordAddr = D[15:11];	//адрес ОУ
wire curWordDoTransmit = D[10];		//признак "приём-передача" (1 значит мы должны передавать данные)
wire [4:0] curWordSubAddr = D[9:5];	//подадрес
wire [4:0] curWordWordsCount = D[4:0];	//число слов данных (0 означает 32), при подадресах 00000 или 11111 это "команды управления"

reg MessageError = 1'b0; //становится одним из флагов в ответном слове

localparam sIdle 	= 2'b00;
localparam sReceive 	= 2'b01;
localparam sReply 	= 2'b10;
localparam sTransmit	= 2'b11;
	
reg [1:0] State = sIdle;
wire isIdle 	= (State == sIdle);
wire isReceive 	= (State == sReceive);
wire isReply 	= (State == sReply);
wire isTransmit = (State == sTransmit);
	
reg DoWeNeedToTransmit = 1'b0;
always @(posedge clk) if (isIdle)
	DoWeNeedToTransmit <= curWordDoTransmit; 

wire isBroadcast = (curWordAddr == 5'b11111);	
reg rBroadcast = 1'b0;
always @(posedge clk) if (isIdle)
	rBroadcast <= isBroadcast;
	
wire isOurAddr = (OurAddr == curWordAddr)&OurAddrOK | isBroadcast;

wire BeginMessage = DataValid & (~RXisData) & isOurAddr;

wire isServiceCommand = (curWordSubAddr == 5'b1_1111) | (curWordSubAddr == 5'b0_0000);

wire [5:0] WordCount;

assign WordCount[4:0] = isServiceCommand? {4'b0, curWordWordsCount[4]} : curWordWordsCount;
//если команда управления, то либо 0 слов (для команд от 00000 до 01111), либо 1 слово (для команд от 10000 до 11111)
//в противном случае, берём число слов "как есть"
assign WordCount[5] = isServiceCommand? 1'b0 : (curWordWordsCount == 5'b00000);
//число слов "0" мы должны интерпретировать как 32

wire noWordsLeft;
wire MinusOneWord = DoWeNeedToTransmit? (isTransmit & (~TxBusy)) :
										DataValid & RXisData;
lpm_counter WordCounter (
			.clock (clk),
			.cnt_en (MinusOneWord),
			.data (WordCount),
			.sload (isIdle),
			.cout (noWordsLeft) );
	defparam
		WordCounter.lpm_direction = "DOWN",
		WordCounter.lpm_port_updown = "PORT_UNUSED",
		WordCounter.lpm_type = "LPM_COUNTER",
		WordCounter.lpm_width = 6;

always @(posedge clk)
	State <= 	isIdle? 	(BeginMessage? sReceive : sIdle):
			isReceive?	((DoWeNeedToTransmit | noWordsLeft)? (rBroadcast? sIdle : sReply) : sReceive):
			isReply?	sTransmit:
					(~DoWeNeedToTransmit | noWordsLeft | MessageError)? sIdle : sTransmit;
							
assign start 	= State[1];
assign TXisData = State[0];	

reg [3:0] HiMem = 4'b0000;

always @(posedge clk) if (isIdle)
	HiMem <= curWordSubAddr[3:0];
	
assign Addr[8:5] = HiMem;

wire [4:0] LoMem;
lpm_counter LoMemCounter (
			.clock (clk),
			.cnt_en (MinusOneWord),
			.sclr (isIdle),
			.Q (LoMem) );
	defparam
		LoMemCounter.lpm_direction = "UP",
		LoMemCounter.lpm_port_updown = "PORT_UNUSED",
		LoMemCounter.lpm_type = "LPM_COUNTER",
		LoMemCounter.lpm_width = 5;

assign Addr[4:0] = LoMem;		

wire ConnectRTC = (~HiMem[3]) & (LoMem == 5'b00010);

reg [15:0] TxMux = 1'b0;

always @(posedge clk) if (MemReady)
	TxMux <= ConnectRTC? TimeStamp : MemIn;
	
assign Q = TxMux;

assign MemWrReq = DataValid & RXisData & State[0];
assign MemRdReq = start & (~TxBusy);

endmodule


"По отдельности" он синтезируется в 67 ЛЭ. Но если поставить адресную заглушку "прямо внутри ПЛИС":
module MilStdAddrJumper (output [4:0] Addr, output Parity);

parameter Address = 6;
parameter isCorrectParity = 1'b1;

assign Addr = Address;
assign Parity = ^Addr^isCorrectParity;

endmodule


то при синтезе часть логики пообрубает, и выйдет уже 62 ЛЭ.

Памятью мы пока что пользуемся "безраздельно", и для формирования сигнала MemReady мы просто поставили 2 D-триггера подряд, которые задерживают сигнал MemRdReq. Таким образом мы "объясняем" модулю, что после запроса данных с определённого адреса пройдёт 1 такт, пока адрес защёлкнется в память, и ещё 1 такт, когда на выход защёлкнется значение по этому адресу.

Мы уже впихнули часы реального времени, хотя логику для их работы мы ещё не доделали.

И наконец, поставили "фиктивный" передатчик МКО, который на самом деле просто на 10 тактов выставляет сигнал busy (и не принимает другие заказы), выдавая на 16-битном выходе то, что в него защёлкнули:
module MilStdDummyTx (input clk, input [15:0] D, input isData, input start, output busy, output reg [15:0] Q = 1'b0);

	DelayLine DL(
		.clk (clk),
		.start (start & (~busy)),
		.ce (1'b1),
		.sclr (1'b0),
		.working (busy));
	defparam
		DL.Duration = 10;
	
	always @(posedge clk) if (start & (~busy))
		Q <= D;
		
endmodule


DelayLine - это мой модуль, его описание и код вот здесь.

Ну и сообщения с шины мы на симуляции будем формировать "руками", выставляя D, RXisData (0 если командное/ответное слово, 1 если слово данных) и DataValid (собственно сигнал о получении очередного слова по шине).

Память оставляем почти пустой, заносим лишь "заголовки массивов" в их начало:


По адресу 0x020 должен начинаться массив FT (Flight Task, полётное задание), подадрес МКО 1_0001. Сюда должны прийти данные с шины, а по умолчанию поставили значение 0xDEAD, увидев которое в конце сообразим - "что-то пошло не так и сообщение не записалось".

Точно так же, по адресу 0x040 массив LT (Long range Target, мишень дальней дистанции), подадрес МКО 1_0010,
затем по адресу 0x060 массив ST (Short range Target, мишень ближней дистанции), подадрес МКО 0_0011,
по адресу 0x080 массив AF (AFfine matrix, матрица аффинного преобразования), подадрес МКО 1_0100,
по адресу 0x0A0 массив RA (RAw data, "сырые данные" с соседнего комплекта для стереорежима), подадрес МКО 0_0101.

В эти адреса пока помещены значения 0xDEAD.

Далее, имеем 5 адресов, с которых мы должны передавать данные. Это:

0x0C0, массив DA (DAta, целевая информация), подадрес МКО 0_0110, здесь лежит заголовок 0x3C3C,
0x100, массив TM (TeleMetry, телеметрия), подадрес МКО 1_1000, здесь лежит заголовок 0xFF00,
0x120, массив IM (IMage, изображение), подадрес МКО 0_1001, здесь лежит заголовок 0x55AA,
0x140, массив DU (DUmp, дамп памяти), подадрес МКО 0_1010, здесь лежит заголовок 0x33CC,
0x180, массив RA (Raw data, "сырые данные" соседнему комплекту для стереорежима), подадрес МКО 0_1100, здесь лежит заголовок 0x0FF0.

Для начала хотим проверить следующие сообщения:
1. сообщение на чужой адрес. Пущай это будет командное слово 0x0000 (адрес 0, подадрес 0, КШ-ОУ, количество слов 32).
2. сообщение для нас, передача полётного задания. 5 слов:
0x3224 - командное слово, адрес 6, подадрес 1_0001 = 17, количество слов 4, КШ-ОУ,
0xAAAA - заголовок массива,
0x0001
0x0002
0x0003 - ещё 3 слова, не хотелось вдаваться в подробности.

3. сообщение для нас, выдача телеметрии:
0x3701 - командное слово, адрес 6, подадрес 1_1000 = 24, количество слов: 1, ОУ-КШ


4. сообщение для нас, выдача изображения, хотим проверить, что количество слов 00000 интерпретируется как 32:
0x3520 - командное слово, адрес 6, подадрес 0_1001 = 9, количество слов: 32, ОУ-КШ.


5. сообщение для нас, выдача целевой информации, хотим проверить коммутацию "часов реального времени" в нужный момент:

0x34D8 - командное слово, адрес 6, подадрес 0_0110 = 6, количество слов: 24, ОУ-КШ.


За кадром пока остаются команды управления и групповые (широковещательные) сообщения.

Начнём!



Рассматриваем "сверху вниз". clk - понятно, тактовая частота 25 МГц. Далее идёт 3 входных сигнала, которые мы здесь задаём ручками. Это данные по шине, признак "данные или командное/ответное слово" и сигнал прихода очередного слова, DataValid.

Далее, две почти бесполезные строки, OurAddr=6 - мы задали адрес оконечного устройства 6, замечательно, и AddrParity=1 - проверили, что наша "адресная заглушка" правильно его сформировала :) Потом можем выкинуть.

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

TXbusy - сообщение от передатчика о занятости (он не закончил предыдущее слово передавать), TXQ - данные, которые он в данный момент передаёт (исключительно для нашего удобства).

Далее, идёт Addr - адрес для оперативной памяти (как на чтение, так и на запись), который задаёт наш модуль. MemIn - данные из памяти к нам. MemRdReq, MemWrReq - запрос на чтение и на запись, соответственно. И наконец, MemReady - сигнал "как бы" из памяти, что запрошенное значение уже готово, сидит в MemIn.

Mark - это мы должны были ручками подать единичку на середине экспозиции кадра, но пока лениво. sync - должна прийти единичка в момент появления слова данных команды управления "Синхронизация (с СД)". И наконец, TimeStamp - выход с часов реального времени, который должен в нужный момент попасть на вход передатчика.

1. Сообщение на чужой адрес.
Как видно, мы "и ухом не повели". Как было состояние 00 (sIdle), так и осталось, никаких запросов на чтение или на запись не шло, всё тихо.

2. "Получить полётное задание".
Как только пришло командное слово, мы перешли в состояние 01 (sReceive), и сразу же защёлкнули адрес памяти 0x20, куда это сообщение должно будет поступить. Спустя 2 такта мы увидели текущее значение, лежащее по адресу 0x20: 0xDEAD. Ну, пускай.

Приходит первое слово данных, 0xAAAA. На этом же самом такте возникает MemWrReq = 1 - запрос на запись этого слова в память. Благо, адрес мы защёлкнули заблаговременно. И можно видеть, что через 2 такта мы действительно видим новое значение 0xAAAA вместо 0xDEAD. И ещё мы делаем инкремент к адресу, теперь это 0x21.

Таким же образом мы получаем слова 0x0001 на адрес 0x21, затем 0x0002 на адрес 0x22 и 0x0003 на адрес 0x23, и убеждаемся (спустя 2 такта), что они все были записаны.

Через такт после того, как пришло последнее слово 0x0003, мы поняли, что сообщение получено целиком - и пора отправлять ответное слово. Перешли в состояние 10 (sReply), тем самым попросив передатчик отправить командное/ответное слово (не слово данных, т.е TXisData=0). Увы, само ответное слово мы так грамотно и не сформировали: нужно было задать наш адрес и далее все нули, подтверждая, что ошибок нет.

В этот же момент, "на всякий случай" мы запросили чтение из памяти (готовясь к передаче) и перешли-таки в состояние 11 (sTransmit), но уже через такт из него вышли, сообразив, что посылать-то ничего не надо!

Модуль вернулся в состояние sIdle, и только передатчик "продолжает слать ответное слово". Отслеживать его нет особой нужды.

На всякий случай ещё глянем дамп памяти под конец работы:


Да, сообщение помещено куда надо, на адреса 0x20..0x23. Больше ничего не изменилось, и это верно: это было единственное сообщение, где данные передавали нам.

3. выдача телеметрии


Как только поступает командное слово, мы задаём адрес 0x100 для оперативной памяти, переходим в состояние 01 (sReceive), но сразу же из него переходим в 10 (sReply), передавая ответное слово. (опять оно не сформировано правильно, но хотя бы отправляется в нужный момент времени). В этот же самый момент формируется запрос на чтение из памяти, MemRdReq, и через 2 такта приходит MemReady=1. Именно тогда в мультиплексор выходных данных защёлкивается значение из памяти, 0xFF00. А дальше мы ожидаем, когда же передатчик освободится.

Вот он освобождается - и тут же получает следующее задание - слово данных 0xFF00. На следующий такт прибавляется единичка к адресу оперативной памяти, 0x101, но вот незадача - запрос на чтение пришёл до того, т.е мы снова прочитали адрес 0x100! Конкретно сейчас это не играло роли, т.к запрошена была передача всего одного слова. Но это явно недоработка, нужно видимо задержать формирование MemRdReq на один такт.

Ну и как только мы послали слово данных и убедились, что оно последнее - перешли в состояние sIdle. Через некоторое время закончил работу передатчик, и всё вернулось на круги своя.

Давайте сразу исправим ошибку, пока её нашли. MemRdReq было "проводом" (комбинаторной функцией), а сделаем регистром, и самую нижнюю строку кода (не считая endmodule) заменим на:

always @(posedge clk)
	MemRdReq <= start & (~TxBusy);


4. выдача 32 слов данных
Чтобы легче проверить правильность работы, всё-таки занесём в память что-то кроме заголовков и нулей. Не мудрствуя лукаво, числа от 1 до 31 в ДЕСЯТИЧНОЙ форме. Ну, BCD. Типа, 0x0031 - это и будет у нас 31. И запускаем:



Ну да, сначала ответное слово (по-прежнему неправильное, но мы и не трогали его пока), затем заголовок массива, лежащий по адресу 0x120, и потом все остальные слова данных, друг за дружкой. И чем это закончится:


Да, последним идёт слово 31, и на этом всё. Неплохо.

5. выдача целевой информации с меткой времени

По адресу 0x0C0 тоже вставим после заголока массива числа от 1 до 23, причём число 2 должно будет замениться на выходе нулём - "меткой времени" TIMESTAMP. Ща глянем:


С ответным словом ещё смешнее, теперь это не просто нули, а затесавшийся с прошлого раза 0x55AA! А вот затем всё идёт правильно: заголовок массива 0x3C3C, за ним "первое" слово данных (там будет куча флажков-"признаков", какие данные достоверные, а какие нет, режим работы прибора и пр. ), и наконец "второе", которое вместо числа 2 стало нулевым - это как раз метка времени сюда добавилась В ОБХОД ПАМЯТИ. Всё верно.


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

  • Тестируем atan1 на QuatCore

    Пора уже перебираться на "железо" потихоньку. Решил начать с самого первого алгоритма, поскольку он уже был написан на ассемблере. В программу внёс…

  • Формулы приведения, что б их... (и atan на ТРЁХ умножениях)

    Формулу арктангенса на 4 умножениях ещё немножко оптимизировал с помощью алгоритма Ремеза: Ошибка уменьшилась с 4,9 до 4,65 угловой секунды, и…

  • Алгоритм Ремеза в экселе

    Вот и до него руки дошли, причина станет ясна в следующем посте. Изучать чужие библиотеки было лениво (в том же BOOSTе сам чёрт ногу сломит), писать…

  • atan на ЧЕТЫРЁХ умножениях

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

  • Ай да Пафнутий Львович!

    Решил ещё немного поковыряться со своим арктангенсом. Хотел применить алгоритм Ремеза, но начал с узлов Чебышёва. И для начала со своего "линейного…

  • atan(y/x) на двух умножениях!

    Чего-то никак меня не отпустит эта тема, всё кажется, что есть очень простой и эффективный метод, надо только его найти! Сейчас вот такое…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 2 comments