Почему я не спешу продолжать разговор о растактовке выполнения инструкций вычислительным ядром STM8

Давно тому назад я хотел начать разговор о сабже. На тот момент осциллограф у меня был в ремонте, и я был вооружён лишь частотомером. С возвращением осциллографа, я думал, что всё станет яснее. Как вы уже догадались, не стало! :)

Итак, задача наша пока что не менялась: передёргивать ножку со светодиодом и считать, сколько тактов это занимает. Понятное дело, что мы увидеть можем только лишь исполнительную фазу, а процессы заполнения входного буфера конвейера от нас скрыты. В справочных листках на STM8 даже дано разъяснение по этому поводу, но полного понимания оно не дало.

Подопытным контроллером будет STM8L152C6T6, распаянный на плате STM8L Discovery, тактируемый от внутреннего колебатора четырьмя мегагерцами.

Листинг 1
Подряд идут команды записи в соответствующий разряд нуля и единицы. Больше ничего лишнего. В конце цикла идёт команда JPF — безусловного перехода
stm8/
	#include "stm8l152c6.inc"
	#include "mapping.inc"

	segment 'rom'
stack_start.w EQU $stack_segment_start
stack_end.w EQU $stack_segment_end
	; initialize SP
	ldw X,#stack_end
	ldw SP,X

;Переключаемся на тактирование 4 МГц
	mov CLK_CKDIVR,#2

	bset PC_DDR,#7
	bset PC_CR1,#7
	bset PC_CR2,#7		;Output, Push-Pull, Fast Mode
	
	nop
		
loop:
	MOV PC_ODR,#0			
	MOV PC_ODR,#128	        ;1 такт
	MOV PC_ODR,#0    	;2 такта
	MOV PC_ODR,#128	        ;2 такта
	MOV PC_ODR,#128	        ;2 (?) такта
	MOV PC_ODR,#0		;2 такта
	MOV PC_ODR,#128	        ;2 такта
	MOV PC_ODR,#0		;2 такта
	MOV PC_ODR,#128    	;2 (?) такта
	jpf loop		;3 (?) такта

	end

И результат выполнения:
Результат выполнения программы л. 1
Уже интересно! Но самое интересное ещё впереди!

Листинг 2
Листинг отличается лишь тем, что убрали перед началом NOP и в середине две подряд идущих установки в «1» сократили до одной
stm8/
;...
;Здесь всё то же самое
;...
	bset PC_DDR,#7
	bset PC_CR1,#7
	bset PC_CR2,#7		;Output, Push-Pull, Fast Mode
	
	;nop	;Убрали NOP
		
loop:
	MOV	PC_ODR,#0	;2(?) такта
	MOV     PC_ODR,#128     ;1 такт
	MOV	PC_ODR,#0	;2 такта
	MOV     PC_ODR,#128     ;1 такт
	;MOV	PC_ODR,#128	
	MOV     PC_ODR,#0	;2 такта
	MOV	PC_ODR,#128	;1 такт
	MOV     PC_ODR,#0       ;2 такта
	MOV     PC_ODR,#128	;1 такт
	jpf     loop		;3 (?) такта

	end

Результат выполнения. Как видим, первый «столбик» неожиданно изменил ширину (от второго этого мы вполне ожидали, там ведь раньше команда дублировалась). А казалось бы, убрали один лишь NOP, который стоял задолго до этого места и всякое последствие должно было бы стать незаметным ещё N итераций цикла назад! А тут всё вкривь и вкось!
Результат выполнения программы л. 2

Далее. Добьём NOP-ами перед циклом программу так, чтобы первая инструкция цикла располагалась по ближайшему адресу, кратному четырём (по разрядности регистра инструкций на входе конвейера). Больше ничего не меняем. Каждая инструкция из тела цикла в памяти программ занимает по 4 байта. Смотрим, что получилось:
Результат выполнения программы л.2 после выравнивания

Листинг 3
Попробуем NOP-ами расширить площадки, когда напряжение на интересующей ножке нулевое
;nop
		
loop:
	MOV	PC_ODR,#0		
	nop
	MOV     PC_ODR,#128	
	MOV	PC_ODR,#0		
	nop
	MOV     PC_ODR,#128	
	MOV     PC_ODR,#0		
	nop
	MOV	PC_ODR,#128	
	MOV     PC_ODR,#0		
	nop
	MOV     PC_ODR,#128	
	jpf     loop		

Результат выполнения программы л.3
Видим тут «некрасивость» — первая площадка имеет длину три такта, а остальные — по два. Попробуем убрать NOP после первого MOV PC_ODR,#0

Листинг 3а

        ;nop

loop:
	MOV	PC_ODR,#0		
	MOV     PC_ODR,#128	
	MOV	PC_ODR,#0		
	nop
	MOV     PC_ODR,#128	
	MOV     PC_ODR,#0		
	nop
	MOV	PC_ODR,#128	
	MOV     PC_ODR,#0		
	nop
	MOV     PC_ODR,#128	
	jpf     loop

Результат:
Результат выполнения программы л. 3а
Отличия минимальные!



Так что я теперь даже и не знаю, насколько возможно с помощью STM8 подавать импульсы, длительность которых исчисляется долями и единицами микросекунд! Если самое небольшое измение в другом месте программы (ладно бы на языке высокого уровня, но на ассемблере-то!) влечёт за собой такие последствия в других местах программы!
  • +1
  • 14 января 2012, 16:24
  • Deer

Комментарии (35)

RSS свернуть / развернуть
Хороший обзор.
Мда, у них походу такая-же проблема как и с армами.
Интересно как на них лепить софтварные протоколы??
На AVR и PIC это плевое дело.
+1
  • avatar
  • a9d
  • 14 января 2012, 16:47
Мне кажется, что на STM8 стОит, если уж надо делать программную реализацию протокола, реализовывать исключительно синхронные протоколы, чтобы все ведомые устройства дожидались нужного фронта тактового сигнала. Потому что тут неопределённости на 1-2 такта будут почти гарантированно. И вылавливать их осциллографом и пытаться корректировать NOP-ами или ещё как-либо — почти бесполезный труд, imho.

С более длинными импульсами и выдержками времени, полагаю, таймеры помогут работать более стабильно!
0
Вообще как ного надо софтверных тайминговых протокогов? Далас, ибуттон, что ещё? разве без них не обойтись? то что нужно, распространено и присутствует в хардваре, либо же имеются переходники на более стандартный интерфейс.
0
Самый популярный это 1-wire.
Также мелкие задержки важны при работе с некоторыми датчиками. А тут я смотрю у стмок есть небольшие проблемы.
+1
у стмок есть небольшие проблемы.
Теоретически…
Практически — граничит с неправильным выбором камня по скорости ногодрыга в определённых применениях… «Каждый проц — под свою задачу»… )))
0
Тут скорее особенности либо вычислительного ядра, либо конкретной реализации у STM. С ядром STM32 не знаком абсолютно, разве что слышал, что у STM32 и STM8 предельно похожая периферия => предельно похожее поведение можно ожидать.

Может, кто-нибудь сможет проделать похожие измерения на STM32 и на ARM-е другого производятела? Желательно «измерительный» цикл, конечно, написать на ассемблере «для чистоты эксперимента»
0
Это не от переферии зависит а от ядра. В армах это на порядок хуже проявляется.
Там посчитать количество тактов до компиляции практически нереально.
0
Ну, по крайней мере, можно сделать несколько раз подряд одинаковых переводов в ноль и в единицу и оценить, насколько разная будет ширина этих «столбиков». Потом поэкспериментировать с добавлением команд как в цикл, так и перед и после него…
0
И какой в этом смысл, если результат известен заранее??? ))))
Ещё раз… всё по задаче… если нужно учитывать каждый цикл, то лучше взять PIC24… там каждый цикл просчитать можно, но… даже там PLL даст джиттер, и найдётся применение, где этот джиттер недопустим…
Если честно… мне непонятна эта паника по поводу +-цикл… таких применений не так уж много… и если уж никак — неправильный выбор камня на стадии проектирования… остальное от лукавого… )))))
«Предупреждён — значит вооружён»…
0
«Разве без них не обойтись ?» Элементная база сейчас довольно широка. Те же термометры можно взять с интерфейсом I2C, который реализован аппаратно и не требует для себя жестких таймингов (интерфейс) и времени ядра (что гораздо больший плюс).
Если есть желание ногодрыгания — используйте авр на здоровье. Только тогда врятли вы сможите спокойно использовать С/С++, обязательны ассемблерные ставки будут — что некрасиво, непонятно и не переносимо.
Так что я согласен. У стмок есть небольшие проблемы.
0
Да, вроде уже обсосали эту тему и не раз.
За счет трех уровненного конвейера и получаются такие вещи.
+1
  • avatar
  • ZiB
  • 15 января 2012, 05:44
Добавь ещё ссылку на доку (описана работа конвейера):
ziblog.ru/wp-content/plugins/download-monitor/download.php?id=48
п.с. не смог найти оригинальную ссылку
0
  • avatar
  • ZiB
  • 15 января 2012, 06:18
Спрашивал вчера в форуме

Простая программа которая тоже дёргает ногой:


   1                     ; C Compiler for STM8 (COSMIC Software)
   2                     ; Parser V4.9.10 - 10 Feb 2011
   3                     ; Generator (Limited) V4.3.6 - 15 Feb 2011
   4                     ; Optimizer V4.3.5 - 15 Feb 2011
  17                     ; 35 void main(void)
  17                     ; 36 {
  18                     	scross	off
  19  0000               _main:
  21                     ; 37 	CLK->CKDIVR = 0; // HSI 16Mhz
  22  0000 725f50c6      	clr	20678
  23                     ; 38 	PORT->DDR |= (1 << PIN); // Output
  24  0004 72185011      	bset	20497,#4
  25                     ; 39 	PORT->CR1 |= (1 << PIN); // Push-pull
  26  0008 72185012      	bset	20498,#4
  27                     ; 40 	PORT->CR2 |= (1 << PIN); // Output speed up to 10 MHz 
  28  000c 72185013      	bset	20499,#4
  29  0010               L3:
  30                     ; 44 		PORT->ODR |= (1 << PIN);
  31  0010 7218500f      	bset	20495,#4
  32                     ; 45 		PORT->ODR &= (uint8_t)~(1 << PIN);
  33  0014 7219500f      	bres	20495,#4
  35  0018 20f6          	jra	L3
  37                     	xdef	_main
  38                     	end


Независимо от наличия строчки PORT->CR2 |= (1 << PIN); частотометр в мультиметере показывает 2,6 МГц (по идее без неё должно быть меньше, с ней — больше).

Это тот же эффект?
0
" Output speed up to 10 MHz" влияет на длительность переходных процессов (фронты сигналов), но не на скорость работы.
Я думаю можно отнести низкую частоту переключения к «указанному» эффекту :)
Т.е. логично, что в зависимости от последовательности и длинны команд, количество тактов на выполнение команд будет разное. Конвейер мать его.
0
Прогнал в симуляторе — по тактам, которые показывает System clock ___ cpu ticks совпадает с количеством тактов на команду. Частота получается слишком низкой, вот этот кусок весь за 5 тактов выполняется:


  30                     ; 44           PORT->ODR |= (1 << PIN);
  31  0010 7218500f             bset    20495,#4
  32                     ; 45           PORT->ODR &= (uint8_t)~(1 << PIN);
  33  0014 7219500f             bres    20495,#4
  35  0018 20f6                 jra     L3
0
В моём случае проблема была в другом — принципиальном непонимании что будет 1 периодом сигнала в этом случае, собрал демку на AVR, потом нашел объяснение для AVR вот тут: www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1230286016/18#18
Для STM8 принцип тот же.
0
Проверил аналогичный код в зависимости от предыдущих команд перед циклом (от выравнивания) получал от пяти до шести тактов, т.е. при 16 МГц
3,2 МГц и 2,66 МГц.
0
Да, так и должно быть :) Мне привидилось, что должно быть по-другому :)
0
По теме статьи:
В комплекте с STVD идёт документ PM0044 STM8 CPU programming manual старой ревизии. В новом документе: www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/PROGRAMMING_MANUAL/CD00161709.pdf
(ревизия 3)

Есть раздел Pipelined execution, там есть несколько примеров с растактовкой + в разделе 5.4 есть импирическая формула для расчёта количества тактов…

Although the decode and/or execute stage of some instructions may take a different number
of cycles, a simplified convention providing a good match with reality, has been used in this
section:
● The decode stage of each instruction takes one cycle only
● The execution stage takes a number of cycles equal to

Cy = DecCy + ExeCy – 1

Where
Cy is the number of execution cycles. In case of decode and execute cycles, It
corresponds to the minimum number of cycles needed by the instruction itself, and
does not take into account the impact of the instruction sequence.
DecCy is the exact number of decode cycles.
ExeCy is the exact number of execute cycles.
0
Подозреваю, что из-за этого единственный надежный способ генирировать задержки — таймером, например TIM4. В библиотеке для STM8L есть пример, а для STM8S — нет :)
0
то что примера нет, не значит что нет таймера :) а если нет именно TIM4, то есть TIM1, TIM2,…
0
Видел. То ли лыжи не едут, то ли я очень глупый. Но до конца не въехал.

Потом решил, что занимаюсь фигнёй. Потом подумал, что кой-чего намеренного есть. Что рано или поздно кто-нибудь ещё задумается на ту же тему. Поэтому в блог для всякой фигни таки выложил свои наблюдения. Чтобы следующим облегчить первые шаги забега по граблям и, может, слегка сориентировать в направлении :)

А для более-менее больших выдержек уже можно мутить хотя бы на таймерах различные выдержки.
0
В целом в рассмотренном примере разнича во времени выполнения команд идет из-за кеша, я не наблюдаю команд с разным временем выполнения. Тот же единственный переход безусловный — выполнение так же за определённое время. (То ли дело условные переходы, предсказание и сброс конвеера).
У меня просьба к автору, как к владельцу осциллографа (и возможно желания эксперементировать). Сможете ли вы провести подобное исследование с выполнением кода из оперативки?
— Шина там 8 бит, следовательно время выборки не зависит от положения инструкции (на границе 4байт или нет), зависит от размера инструкции.
— Время декодирования так же величина постоянная (на сколько мне известно в стм декодер не использует повторно декодированные инструкции, не интел всё же).
— Время исполнения так же постоянное (по доке).
— Сбросов конвеера так же нет (условных переходов нет).
Все шансы на постоянное время выполнения кода. Это возможно будет интересно любителям ногодрыгства, мне это только проверить понимание архетиктуры. Сам такой эксперимент провести не смогу, не имею осциллографа, но и на проведение не настаиваю.
0
ОК, померим через несколько дней…
0
Программа написана и из SRAM даже выполняется! Завтра сдам экзамен и для Вас померю, коллега! :)
0
Переселил в RAM цикл (см. листинг), сделал тактирование 4 МГц

Main:
	MOV PC_ODR,#0
	MOV PC_ODR,#128
	MOV PC_ODR,#0
	MOV PC_ODR,#128
	MOV PC_ODR,#0
	MOV PC_ODR,#128
	MOV PC_ODR,#0
	MOV PC_ODR,#128
	jra Main


Выполняется тот же цикл из RAM

Идеально ровно, но безобразно медленно!
0
Гм, порядка 16 тактов на команду? Да, довольно невкусно.
0
Да, из RAM команда MOV (long),(imm) исполняется ровно 16 тактов.
Хм… А ведь при таком быстродействии таким образом 1-wire реализовывать — в самый раз! :)

Тем более, что в RAM можно перезаписывать кусок кода, критичный ко времени исполнения, исполнять его, потом на то же место поселять любой другой кусок кода!
0
Примного благодарен.
По скорости исполнения в даташите сразу отмечено, что шина используется для кода и для данных одна, да к тому же и 8 бит вместо 32 (флешевских). Да, USB софтверный не реализуешь, да и думаю это мы переживем, можно взять арм с хардверным — разница в цене не столь высока, исли устройству нужен USB.

А Вместо MOV наверно лучше будет использовать битовые команды в данном случае (bset или как-то так). они генерятся если делать через битовые поля структур для портов IO. Там команда 4 байта, но зато работа только с указанным битом и операция атомарная.

Еще раз благодарю за проверку.
0
Ну, всё-таки, по тому даташиту, исполнение команды должно не раза в три растянуться, а на несколько тактов. Ведь 4 байта (а и инструкции BSET, BRES, BCPL, и MOV занимают в памяти по 4 байта из флеша загружаются за 1 такт, а из RAM — за 4. А время декодирования и выполнения, по идее, должно по такту оставаться. Итого 6 тактов. А тут все 16 набираются…
0
Кстати, ЕМНИП у этих МК есть настройки флеша и его акселератора, которые, как я понял, на растактовку и влияют. Мож если их настройки покрутить, то будет получше?
0
  • avatar
  • Vga
  • 23 января 2012, 20:26
Не будет… Воспринимайте это как неизбежное…
0
Может быть, хотя фиг знает.

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

Теперь хочу сделать один Проект на этом камушке и заняться другими делами. А их предостаточно! Некоторые из них ещё обязательно сопроводятся более увлекательными статьями, чем тупление в осциллограммы и выводы из них! :)
+1
И все же, прогони пару экспериментов с разными настройками флеша. Интересно же.
0
Хорошо, сделаем! Но не в самое ближайшее время… [в течение недели очень постараюсь!]
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.