nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Мучаем 5576ХС4Т - часть 'h33 - ускоряем ВСЁ

Часть 0 - покупаем, паяем, ставим драйвера и софт
Часть 1 - что это вообще за зверь?
Часть 2 - наша первая схема!
Часть 3 - кнопочки и лампочки
Часть 4 - делитель частоты
Часть 5 - подавление дребезга кнопки
Часть 6 - заканчиваем кнопочки и лампочки
Часть 7 - счетчики и жаба
Часть 8 - передатчик UART
Часть 9 - Hello, wolf!
Часть 'hA - приёмник UART
Часть 'hB - UART и жаба
Часть 'hC - полудуплексный UART.
Часть 'hD - МКО (МКИО, Mil-Std 1553) для бедных, введение.
Часть 'hE - приёмопередатчик МКО "из подручных материалов" (в процессе)
Часть 'hF - модуль передатчика МКО
Часть 'h10 - передатчик сообщений МКО
Часть 'h20 - работа с АЦП ADC124s051
Часть 'h21 - преобразование двоичного кода в двоично-десятичный (BCD)
Часть 'h22 - Bin2Bcd с последовательной выдачей данных
Часть 'h23 - перемножитель беззнаковых чисел с округлением
Часть 'h24 - перемножитель беззнаковых чисел, реализация
Часть 'h25 - передаём показания АЦП на компьютер
Часть 'h26 - работа над ошибками (быстрый UART)
Часть 'h27 - PNG и коды коррекции ошибок CRC32
Часть 'h28 - передатчик изображения PNG
Часть 'h29 - принимаем с ПЛИС изображение PNG
Часть 'h2A - ZLIB и коды коррекции ошибок Adler32
Часть 'h2B - ускоряем Adler32
Часть 'h2C - формирователь потока Zlib
Часть 'h2D - передаём сгенерированное PNG-изображение
Часть 'h2E - делим отрезок на равные части
Часть 'h2F - знаковые умножители, тысячи их!
Часть 'h30 - вычислитель множества Мандельброта
Часть 'h31 - ускоренные сумматоры
Часть 'h32 - ускоренные счётчики (делаем часы)
Часть 'h33 - ускоряем ВСЁ
Часть 'h34 - ускоренные перемножители
Часть 'h35 - умножители совсем просто
Часть 'h36 - уравновешенный четверичный умножитель


Конкретнее - схему из части 'h25, которая каждую секунду передаёт на компьютер показания четырёх каналов АЦП, причём в понятной человеку форме, что потребовало умножения на константы, чтобы из "попугаев" получить вольты, а затем преобразовать двоичный код в строку с десятичной записью числа (включая запятую там, где мы скажем), запихать в нужное место в памяти и, наконец, передать по UART.

Когда я попытался синтезировать эту схему для кристалла EPF10K200SRC240-3 (аналогом которого является наша 5576ХС4Т), оказалось, что тайминги не соблюдаются практически по всей схеме! Где-то сами модули слишком медленные, где-то они по отдельности показывают приличную скорость, но из-за наличия комбинаторных выходов, соединение их между собой приводит к появлению слишком длинных путей.



Пришлось основательно покопаться в этих модулях, благодаря чему схема теперь может "честно" работать на самом медленном кристалле на частоте 80 МГц, правда, заставить перемножитель с 32-битным аккумулятором выдерживать эти тайминги так и не удалось, пришлось снизить ширину аккумулятора до 28 бит, что внесло дополнительную ошибку со ср. кв. значением 1/100 цены младшего разряда :)


Поскольку мы запускаем АЦП раз в 4 секунды, то "старые" делители частоты с общей разрядностью 25 бит не укладывались в нормативы. В прошлой части мы уже предложили FastCounter, но у него слишком много выходов, а на "принципиальной схеме" нельзя оставлять входы неподключёнными, что страшно неудобно - каждый раз приходится лезть в библиотеку компонентов и доставать оттуда Gnd и VCC.

Поэтому здесь мы применили новый модуль FastFreqDivider:
`include "math.v"

module FastFreqDivider (input clk,   //clock
			input ce,    //count enable
			output ceo); //count enable - output

	parameter Fin = 80_000_000;
	parameter Fout = 8_000_000;
	
	localparam DivideBy = (Fin + Fout / 2) / Fout; //rounding to nearest integer
	localparam IntWidth = `CLOG2(DivideBy - 1); //here we don't have to honestly show integer 0 up to DivideBy-1, so we can cheat a little :)
	localparam MaxInt = 1 << IntWidth;
	localparam InitVal = MaxInt - DivideBy + 1; 

	wire combTC;	
	reg TC = 1'b0;

	lpm_counter	lpm_counter_component (
				.clock (clk),
				.cnt_en (ce),
				.sset (ceo),
				.cout (combTC));
	defparam
		lpm_counter_component.lpm_direction = "UP",
		lpm_counter_component.lpm_port_updown = "PORT_UNUSED",
		lpm_counter_component.lpm_type = "LPM_COUNTER",
		lpm_counter_component.lpm_width = IntWidth,
		lpm_counter_component.lpm_svalue = InitVal;

	
	always @(posedge clk) if (ce)
		TC <= combTC;
		
	assign ceo = TC & ce;

endmodule


Это не просто "обкусанная" версия FastCounter: здесь мы решили избавиться от компаратора, для чего нужно считать не от нуля до DivideBy-1, а начинать с MaxInt - DivideBy + 1, доходить до максимального значения (все биты - единицы), чтобы там сформировался сигнал cout. Его мы запихиваем в регистр, и к следующему такту (когда мы досчитаем уже до нуля) он появляется на выходе и заставляет счётчик снова установиться в значение MaxInt - DivideBy + 1.

Мы не могли себе такого позволить, когда непосредственно выход счётчика "идёт наружу" - если часы вместо цифр 0..9 начнут показывать 7, 8, 9, A, B, C, D, E, F, 0, а потом снова сбрасываться в 7 - мы будем не очень счастливы... Но здесь порядок чисел не имеет роли, главное сосчитать сколько надо импульсов!

Но честно говоря, особенных преимуществ такая реализация не имеет. На маленьких счётчиках она даже чуть крупнее (на 1-2 ЛЭ), чем FastCounter, на более крупных (скажем, 17 бит) начинает выигрывать те же 1-2 ЛЭ.

В своей безграничной жадности мы решили, что деление частоты в 2N + 1 раз в такой схеме потребует счётчик на N бит. Например, однобитный счётчик может делить частоту в 3 раза! Действительно, MaxInt = 2 (сколько значений представимо таким счётчиком), DivideBy = 3 (во сколько раз поделить), значит вход синхронной установки заставляет счётчик обнулиться (MaxInt - DivideBy + 1 = 0).

Итак, первое число - 0. Затем идёт 1, при этом появляется сигнал переноса. На третьем такте счетчик снова показывает 0, а на выходе появляется "задержанная" регистром единица с разряда переноса. Она же заставляет к следующему такту снова обнулить счётчик. Поэтому мы наблюдаем последовательность

0, 0, 1, 0, 0, 1, 0, 0, 1, ...


то есть, он действительно делит в 3 раза :) Ничего удивительного - когда мы начали задерживать разряд переноса, он и позволили "удлинить" счёт на единичку. Но в FastCounter мы не стали выпендриваться, потому что числа на выходе были бы неправильными: в случае счёта до 3 мы ожидали бы увидеть
0, 1, 2, 0, 1, 2, ....


А здесь сделали. Но пока ни разу не пригодилось, и скорее всего не пригодится, зато жаба сытая.

Модуль SPI для ADC124s051 сам по себе шустрый (до 120 МГц на нашем медленном кристалле), но имел комбинаторный выход finished, из-за чего его присоединение к последующим модулям уже не проходило нормы по задержкам (макс. частота 59,17 МГц вместо 80 МГц). Пришлось его малость подправить:

module SPI_for_ADC124s051 (input clk, input ce, input [1:0] Ain, input start, inout MISO,
			   output nCS, output SCK, output MOSI, output reg [11:0] Q = 1'b0, output reg finished = 1'b0, output reg [1:0] Chan = 2'b00);

	localparam sIdle =  5'b00000;
	localparam sStart = 5'b01111;
	localparam sB0 =    5'b10000;
	localparam sB1 =    5'b10001;
	localparam sB2 =    5'b10010;
	localparam sB3 =    5'b10011;
	localparam sB14 =   5'b11110;
	localparam sB15 =   5'b11111; 

	wire [4:0] State;
	
	assign nCS = (~State[4])&(~State[3]);
	wire IsDataBit = State[4];
		
	wire IsFinalBit;
	
	lpm_counter	counter (
				.clock (clk),
				.cnt_en (slow_ce),
				.sset (start),
				.q (State),
				.cout (IsFinalBit) );
	defparam
		counter.lpm_direction = "UP",
		counter.lpm_port_updown = "PORT_UNUSED",
		counter.lpm_type = "LPM_COUNTER",
		counter.lpm_width = 5,
		counter.lpm_svalue = 5'b01111;													

	reg rSCK = 1'b0;

	wire slow_ce = ce & rSCK; 
	wire MISO_ce = ce & (~rSCK);
	
	wire EnableShiftReg = slow_ce | start; //at final bit we probably loaded new portion of data. Shouldn't shift it already!

	assign MOSI = Q[11];
	assign MISO = nCS? 1'b0 : 1'bz; //sinking MISO when chip is not active. NO DANGLING WIRES!!!	
	wire almost_finished = IsFinalBit & MISO_ce;
	assign SCK = rSCK & IsDataBit;

	reg [1:0] NextChan = 2'b00;
		
	always @(posedge clk) begin
		rSCK <= nCS? 1'b0 : ce? ~rSCK : rSCK; 
		if (EnableShiftReg)
			Q[11:1] <= start? {4'bxxx0, Ain, 5'bxxxxx}: Q[10:0];
		if (MISO_ce)
			Q[0] <= MISO;
		if (start) begin
			Chan <= NextChan;
			NextChan <= Ain;
		end
		finished <= almost_finished;
	end

endmodule


Теперь сигнал Finished формируется чуть-чуть раньше. Вот как было:


и как стало:


Дело в том, что задержать старый сигнал на один такт было никак нельзя: к тому времени уже менялось значение на выходе, а "защёлкивать" ещё раз 12-битный выход ой как не хотелось. Поэтому вместо формирования сигнала по отрицательному фронту SCK, мы стали формировать его по положительному, и заносить в регистр. Количество логических элементов в схеме вообще не поменялось, так и осталось 33 ЛЭ.

Очередную свинью подложил простейший модуль ADCconstants - поначалу он был чисто комбинаторный, потом у нас возникла проблема с десятичной запятой (она оказалась напрямую связана с входом записи в память WR_EN, см. работу над ошибками). Теперь и на другой выход "пришла жалоба", на выход DigitAddr, указывающий адрес в памяти, куда нужно положить число. Ничего страшного, защёлкнем и его:
module ADCconstants (input clk, input [1:0] Chan, output [15:0] Mult, output reg [6:0] DigitAddr, output reg [1:0] DecimalPos = 1'b0);

assign Mult =  	(Chan == 2'b00)? 16'd13848 : //12 V
		(Chan == 2'b01)? 16'd55298 : //5 V
		(Chan == 2'b10)? 16'd30695 : //1,8 V 
				 16'd36541;  //3,3 V 
							 
wire [6:0] uDigitAddr = (Chan == 2'b00)? 7'h46 : //12 V
			(Chan == 2'b01)? 7'h33 : //5 V
			(Chan == 2'b10)? 7'h0D : //1,8 V
					 7'h21;  //3,3 V
									 
wire [1:0] uDecimalPos = (Chan == 2'b00)? 1'd1 : 1'd0;
always @(posedge clk) begin
	DecimalPos <= uDecimalPos;
	DigitAddr <= uDigitAddr;
end
	
endmodule


Ещё одной порцией предупреждений стало меньше...

Разумеется, самой большой редиской был BankerRoundingMultiplier с 32-битным аккумулятором - он показывал максимально допустимую частоту 57,47 МГц, вместо требуемой нам 80 МГц. Увы, даже заменив "обычный" аккумулятор драндулетом "с выбором переноса" (см ускоренные сумматоры), избавиться от предупреждений так и не удалось - не хватало последних 5 МГц.

Не стал пока совсем усердствовать и решил смириться на чуть уменьшенной ширине аккумулятора. В первую очередь, нужно было сделать ускоренный сумматор с произвольной шириной шины (в прошлый раз он был 32-битным):

module CarrySelectAdd (input [Width - 1 : 0] A, input [Width - 1 : 0] B, input cin, output [Width - 1 : 0] Q, output cout);

	parameter Width = 23; //should be more than 16
	localparam HalfWidth = (Width + 1) / 2; //we're greedy after all, let's give odd bit to LSB part
	//for 32, we get 16
	localparam AnotherHalf = Width - HalfWidth;
	//for 32, we get 16

wire IntCout;
lpm_add_sub BL0 (	.cin (cin),
			.dataa (A[HalfWidth - 1 : 0]),
			.datab (B[HalfWidth - 1 : 0]),
			.cout (IntCout),
			.result (Q[HalfWidth - 1 : 0]));
defparam
	BL0.LPM_WIDTH = HalfWidth;
					
wire [AnotherHalf - 1 : 0] res1_0;
wire cout1_0;
//case of no carry in
lpm_add_sub BL1_0 (	.cin (1'b0),
			.dataa (A[Width - 1 : HalfWidth]),
			.datab (B[Width - 1 : HalfWidth]),
			.cout (cout1_0),
			.result (res1_0));
defparam
	BL1_0.LPM_WIDTH = AnotherHalf;
						
wire [AnotherHalf - 1 : 0] res1_1;
wire cout1_1; 
//case of carry in
lpm_add_sub BL1_1 (	.cin (1'b1),
			.dataa (A[Width - 1 : HalfWidth]),
			.datab (B[Width - 1 : HalfWidth]),
			.cout (cout1_1),
			.result (res1_1));
defparam
	BL1_1.LPM_WIDTH = AnotherHalf;
					
assign Q[Width - 1 : HalfWidth] = IntCout? res1_1 : res1_0;
assign cout = IntCout? cout1_1 : cout1_0;

endmodule


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

Далее, модифицируем более простой перемножитель беззнаковых чисел (см часть 'h24), чтобы он использовал этот сумматор:

module FastCheapUnsignedMult16 (input clk, input [15:0] B, input [15:0] C, input start, output [15:0] Q, output reg finished = 1'b0);

parameter AccWidth = 23; //pretty decent performance (rms error = 0.108 of LSB, increases overall error from rounding to 16 bit just by 10%)
//it MUST be greater than 16, otherwise it doesn't synthesize properly (and you surely don't want such an ACC which discards one bit just to begin with!)

//maximum sane value is 31: when not using banker's rounding, we don't need lesser digit because it can't have carry-out anyway

//state machine
	wire [4:0] State;
	//5'b00000 is Idle
	//5'b01110 is start
	//5'b11111 is finished state
	wire IsIdle = (~State[4])&(~State[3]);
	wire AlmostFinished;
	
	lpm_counter	counter (
				.clock (clk),
				.cnt_en (~IsIdle),
				.sset (start),
				.q (State),
				.cout (AlmostFinished) );
	defparam
		counter.lpm_direction = "UP",
		counter.lpm_port_updown = "PORT_UNUSED",
		counter.lpm_type = "LPM_COUNTER",
		counter.lpm_width = 5,
		counter.lpm_svalue = 5'b01111;
		

//data path
	reg [15:0] CSR = 1'b0; //shift register for 2nd operand
	reg [AccWidth - 2 : 0] BSR = 1'b0; //senior is not needed after all
	reg [AccWidth - 1 : 0] Acc = 1'b0; //but here it's needed all right
	
	wire [AccWidth - 1 : 0] ALUout;
	CarrySelectAdd ALU (    .A (Acc),
				.B ({1'b0, BSR}),
				.cin (1'b0),
				.Q (ALUout),
				.cout () );
	defparam
		ALU.Width = AccWidth;


	always @(posedge clk, posedge start) begin
		if (start)
			Acc <= 1'b1 << (AccWidth - 17);
		else if (CSR[15])
			Acc <= ALUout;
	end
	
	always @(posedge clk) begin
		CSR <= start? C : CSR << 1;
	
		BSR <= start? B << (AccWidth - 17) : BSR >> 1;
		
		finished <= AlmostFinished;
	end
	
	assign Q = Acc[AccWidth - 1: AccWidth - 16];
endmodule


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

При ширине аккумулятора в 28 бит предупреждений ещё не возникает. Жить можно.

Преобразователь двоичного кода в строку с его десятичной записью Bin2Bcd16withFixedPoint сам по себе оказался быстрым (120 МГц), но как всегда, имел комбинаторные выходы char, DigitReady и DigitPos (пока не задействован), которые приводили к задержкам (77 МГц). Тут, поскольку и выход данных, и выход, сигнализирующий об их поступлении, были комбинаторные, мы просто поставили "защёлки" и на тех, и на других:

//Possible values for DecimalPointPos:
//00 - d,dddd
//01 - dd,ddd
//10 - ddd,dd (these 3 should be enough for volts, amperes, ohms etc. (as we can use milli, micro, kilo, etc)
//11 - ddddd 

module FastBin2Bcd16withFixedPoint (input clk,
                                input [15:0] D,
                                input start,
                                input [1:0] DecimalPointPos,
                                output reg [7:0] char = 1'b0,
                                output reg DigitReady = 1'b0,
                                output [2:0] DigitPosition,
                                output reg Finished = 1'b0);
                                
parameter DecimalPointChar = ",";
//first, state machine
localparam sIdle  =       5'b00000;
localparam sStart =       5'b00001;
localparam sDigit0Ready = 5'b10000; //senior, tens of thousands
localparam sShift1_1    = 5'b10001;
localparam sShift1_2    = 5'b10010; //nice place for outputting decimal point, if needed
localparam sShift1_3    = 5'b10011;
localparam sDigit1Ready = 5'b10100; //thousands
localparam sShift2_1    = 5'b10101;
localparam sShift2_2    = 5'b10110;
localparam sShift2_3    = 5'b10111;
localparam sDigit2Ready = 5'b11000; //hundreds
localparam sShift3_1    = 5'b11001;
localparam sShift3_2    = 5'b11010;
localparam sShift3_3    = 5'b11011;
localparam sDigit3Ready = 5'b11100; //tens
localparam sShift4_1    = 5'b11101;
localparam sShift4_2    = 5'b11110;
localparam sShift4_3    = 5'b11111; //Digit3 will be ready exactly when we're back to Idle!

wire [4:0] State;

wire IsIdle = (State == sIdle); //no shortcuts, all states are used!
wire TC; //Terminal Count
reg AlmostFinished = 1'b0;
always @(posedge clk) begin
	AlmostFinished <= TC;
	Finished <= AlmostFinished;
end

wire DoCorrection = ~State[4]; //we do correction on conversion steps, but not when showing results
assign DigitPosition = {Finished, State[3:2]};

wire ShowingDP = (DecimalPointPos == State[3:2]) & (DecimalPointPos != 2'b11) & State[4] & State[1] & (~State[0]);

wire CombDigitReady = AlmostFinished | (State[4] & (~State[1]) & (~State[0])) | ShowingDP;
always @(posedge clk)
	DigitReady <= CombDigitReady;

lpm_counter	counter (
				.clock (clk),
				.cnt_en (~IsIdle | start),
				.q (State),
				.cout (TC) );
	defparam
		counter.lpm_direction = "UP",
		counter.lpm_port_updown = "PORT_UNUSED",
		counter.lpm_type = "LPM_COUNTER",
		counter.lpm_width = 5;

//now our datapath
wire [19:0] Q;
reg [10:0] SR = 1'b0; //shift register

reg [3:0] senior = 1'b0; //[19:16]
wire [7:0] comb_char = ShowingDP? DecimalPointChar : {4'h3, senior};
always @(posedge clk)
	char <= comb_char; //to avoid extra-large propagation path from SPI channel through 


wire B0toB1;
SeqBinToBcdLeastSignBlock Block0 (
	.clk (clk),
	.sclr (start),
	.D (D[15]),
	.DoCorrection (DoCorrection),
	.bIn (SR[10]),
    .Q (Q[3:0]),
    .bOut (B0toB1));

wire B1toB2;
SeqBinToBcdAtomicBlock Block1 (
	.clk (clk),
	.sclr (start),
	.DoCorrection (DoCorrection),
	.bIn (B0toB1),
    .Q (Q[7:4]),
    .bOut (B1toB2));

wire B2toB3;
SeqBinToBcdAtomicBlock Block2 (
	.clk (clk),
	.sclr (start),
	.DoCorrection (DoCorrection),
	.bIn (B1toB2),
    .Q (Q[11:8]),
    .bOut (B2toB3));
    
wire B3toB4;
SeqBinToBcdAtomicBlock Block3 (
	.clk (clk),
	.sclr (start),
	.DoCorrection (DoCorrection),
	.bIn (B2toB3),
    .Q (Q[15:12]),
    .bOut (B3toB4));

always @(posedge clk) begin
	senior <= start? D[3:0] : {senior[2:0], B3toB4};
	SR     <= start? D[14:4]: {SR[9:0], senior[3]};
end

endmodule


(детали реализации см. в части 'h21, части 'h22 и части 'h25)

Следующий тормоз - это передатчик строки UART UARTstringTransmitter. Он получал байт из памяти, и тут же проверял, хранится ли там ноль, который служит индикатором конца строки? Именно результат этой проверки подавался в качестве управляющего сигнала на регистры. Задержка сигнала из блока памяти в логический элемент уже достаточно велика на этом кристалле, а когда мы ставим два каскадированных элемента, чтобы из 8 входов получить 1 выход (char == 0), это становится последней каплей. Но потом ещё и результат этого сравнения непосредственно управляет подачей запускающего импульса на передатчик UART! Согласно Timing Analyzer, максимальная частота составляла 56,5 МГц.

Этот модуль мне давно не нравился - он, запустив передачу одного символа, возвращался в исходное состояние, пока не получит сигнала окончания символа. В этот момент он, не уверенный, успел ли новый символ прийти из памяти, снова отсчитывал 2 такта, и если там не ноль, продолжал передачу. Добавь я ещё один такт на "защёлкивание" символа, чтобы потом спокойно его сравнить с нулём, а потом и ещё один такт на "защелкивание" сигнала запуска - уже могли начаться проблемы с передачей на высокой скорости (921 600 бод). Наверное, ничего страшного, подумаешь, очередные 0,2% отклонения от заданной скорости, но был ещё один "прикол" с незапамятных времён, когда передатчик UART начинал работать сразу при подаче питания, посылая последовательность из всех единиц (т.е без стартового бита - только биты данных, все единицы, и стоповый бит - тоже единица!). И всё бы ничего - там и должна быть единица - но потом он посылал сигнал окончания передачи, и мой старый UARTstringTransmitter реагировал на него (он тупо не помнил, давали ли ему команду на запуск!) и мог бы натворить дел, если бы по нулевому адресу в памяти не лежал бы ноль. В общем, давно пора было сделать всё как надо:

//for slower crystals (like "-3" speed grade) and relatively fast clk frequency
//we shorten our comb paths
//let's make mem delay = 2 here, probably don't need to send STRING as long as 12 kilobytes (that's where we would prefer 2 blocks of RAM combined)

module fasterUARTstringControl (input clk,
								input start,
								input [AddrWidth - 1 : 0] Addr,
								input [7:0] char,
								input TxReady,
								output [AddrWidth - 1 : 0] OutAddr,
								output reg UARTst = 1'b0);
								
	parameter AddrWidth = 8;
	
	reg charIsZero = 1'b0;
	always @(posedge clk)
		charIsZero <= (char == 1'b0);
	
	//state machine
	localparam sIdle = 		3'b000;
	localparam sFetch1 = 	3'b001; //have proper addr at output of our module
	localparam sFetch2 =	3'b010; //have proper addr at RAM addr latch
	localparam sFetch3 =	3'b011; //have proper data at 'char' input, computing charNotZero
	localparam sDecision = 	3'b100; //at last have correct charNotZero to make a decision: do we send UARTst pulse? Also adding 1 to addr
	localparam sTransmit =  3'b101; //waiting until TxReady comes in and coming back to sDecision. 
		
	wire [2:0] State; 
		
	wire isDecision = State[2] & (~State[0]); 
	wire DoReturnToIdle = isDecision & charIsZero;
	
	wire isTransmit = State[2] & State[0];
	wire DoReturnToDecision = TxReady & isTransmit; //here we're paranoic. If we know TxReady comes only at isTransmit, it's easier
	//wire DoReturnToDecision = TxReady;
	
	reg r_start =1'b0;
	always @(posedge clk)
		r_start <= start;
	
	wire EnableCount = r_start | (State == sFetch1) | (State == sFetch2) | (State == sFetch3) | (State == sDecision);
	//very complex expression, but it boils down to simple 4-LUT
		
	lpm_counter StateMachine (.clock (clk),
				  .cnt_en (EnableCount), //stay in sIdle (until start), go in fetches/decision, stay in Transmit
				  .sclr (DoReturnToIdle), //returning to Idle
				  .sset (DoReturnToDecision), //returning to Decision
				  .q (State) );
	defparam
		StateMachine.lpm_direction = "UP",
		StateMachine.lpm_port_updown = "PORT_UNUSED",
		StateMachine.lpm_type = "LPM_COUNTER",
		StateMachine.lpm_width = 3,
		StateMachine.lpm_svalue = sDecision;
		
	//address counter
	lpm_counter AddrCnt (	.clock (clk),
							.cnt_en (isDecision), //+1 each time we send one byte
							.sload (r_start),		  //loads init addr when we start
							.data (Addr),		
							.q (OutAddr));
	defparam
		AddrCnt.lpm_direction = "UP",
		AddrCnt.lpm_port_updown = "PORT_UNUSED",
		AddrCnt.lpm_type = "LPM_COUNTER",
		AddrCnt.lpm_width = AddrWidth;
		
	//start impulse. Let's register it, too. It's UART after all, nowhere to hurry, 870 clk counts to transmit 1 byte 
	always @(posedge clk)
		UARTst <= isDecision & (~charIsZero);	
	
endmodule


Теперь всё "по науке" - когда только "защёлкиваешь" туда новый адрес во время запуска, отсчитывается 3 такта, чтобы успел прийти символ из памяти. Если он не нулевой - начинаем передачу и прибавляем единичку к адресу. Как только получили сигнал о завершении передачи - проверяем, нулевой ли следующий символ (теперь 3 такта ждать не надо!), и всё начинается по-новой.

И наконец, наш самый старый модуль SimpleUARTtransmitter тоже вляпался. Тот самый сигнал окончания передачи формировался комбинаторно, после чего шёл далеко-далеко в fasterUARTstringControl, где комбинаторно объединялся с другими сигналами (проверялось, что мы действительно в режиме sTransmit) и только потом шёл в регистры.

Исправляем: "защёлкиваем" сигнал готовности. Пока что - по-простому, т.е не пытаемся его сформировать заблаговременно:

`include "math.v"

module SimpleUARTtransmitter (input clk, input st, input [7:0] Data, output reg txd=1'b1, output reg ready = 1'b0, output DataOutCE);

	parameter CLKfreq = 80_000_000;
	parameter BAUDrate = 19_200;

	localparam DividerBits = `CLOG2((CLKfreq + BAUDrate / 2) / BAUDrate);
	localparam Limit = (CLKfreq + BAUDrate / 2) / BAUDrate - 1;
	//localparam Limit = CLKfreq / BAUDrate - 1;
						
	reg [DividerBits - 1 : 0] FreqDivider;							
							
	reg [7:0] ShiftReg = 8'b1111_1111;

	localparam sB1	    =	4'b0000;
	localparam sB2	    =	4'b0001;
	localparam sB3		=	4'b0010;
	localparam sB4		=	4'b0011;
	localparam sB5		=	4'b0100;
	localparam sB6		=	4'b0101;
	localparam sB7		=	4'b0110;
	localparam sB8		=	4'b0111;
	localparam sStop	= 	4'b1000;
	localparam sIdle 	=	4'b1001;
	localparam sStart 	= 	4'b1111;

	reg [3:0] State = sIdle;

wire isIdle = State[3]&(~State[1])&State[0];

wire ce = ((FreqDivider&Limit) == Limit);

wire i_txd = 	~State[3]? ShiftReg[0] : ~State[2];

wire almost_ready = State[3] & (~State[2]) & ce;

assign DataOutCE = (~State[3]) & ce;

always @(posedge clk) begin
		FreqDivider <= isIdle | ce | st? 1'b0: FreqDivider + 1'b1;
		
		State <= st? sStart : ce? State + 1'b1 : State;
	
		ShiftReg <= st? 					Data :
					DataOutCE?	(ShiftReg >> 1'b1) :
											ShiftReg;
		txd <= i_txd;
		ready <= almost_ready;											
end

endmodule


Вот теперь всё! Результаты Timing Analyzer, применённые к "верхнему уровню" схемы, показывают: допустимая рабочая частота 81,97 МГц. "Дожали"!

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

Да, месяц назад она уже работала, но теперь уж точно проблем возникнуть не должно.



Вот потому-то и надо было поскорее с этим вопросом разобраться, чтобы и всю последующую работу не пришлось бы также кропотливо переделывать! Увы, когда нас в институте учили верилогу, там была ПЛИС Xilinx Spartan3e, с какими-то безумными скоростями, но с генератором тактовой частоты всего на 50 МГц. С ней можно было всё что угодно делать, огроменные комбинаторные цепи накручивать - ей всё было пофиг.

Теперь приходится переучиваться :)
Tags: ПЛИС, работа, странные девайсы
Subscribe

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

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

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

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

  • Потёмкинская деревня - 2

    В ноябре 2020 года нужно было сделать скриншот несуществующей программы рабочего места под несуществующий прибор, чтобы добавить его в документацию.…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments