Полная версия этой страницы:
Свои процессоры
~2K LUT - немного по сравнению с LEON/OpenRISC/Cortex-M и тп.
Без внешней памяти получится запустить что-либо дельное, написанное на Си?
Ynicky
Oct 23 2009, 20:39
Цитата(Leka @ Oct 24 2009, 00:21)

~2K LUT - немного по сравнению с LEON/OpenRISC/Cortex-M и тп.
Так это только ядро. Без кэш программ и данных. Без FPU.
Я в свое время синтезил leon2, точные цифры не помню, но сравнивал со своим процессором.
Результаты были не в мою пользу. Но у меня было много мультимедийных инструкций.
Николай.
Цитата(Leka @ Oct 24 2009, 00:26)

Без внешней памяти получится запустить что-либо дельное, написанное на Си?
Эту версию (MIPS32) еще не применял в серьезных разработках.
Но предыдущий процессор используется в MP3 камкордере со встроенной памятью
128 кБ ПЗУ программ и 64кБ ОЗУ программ/данных (правда в ASIC-е на 0,18мкм).
Николай.
Цитата(Ynicky @ Oct 24 2009, 00:30)

Так это только ядро. Без кэш программ и данных.
Вот и спрашиваю, можно каким Си-компилятором создавать более-менее практичные приложения для системы с несколькими Кбайтами памяти(те без внешней памяти).
Для FPGA, не для ASIC.
Ynicky
Oct 23 2009, 20:47
Цитата(Leka @ Oct 24 2009, 00:44)

Вот и спрашиваю, можно каким Си-компилятором создавать более-менее практичные приложения для системы с несколькими Кбайтами памяти(те без внешней памяти).
Можно, например WEB камера (без звука, в системах безопасности) на основе камкордера занимает у меня 32 кБ кода, написанного на С.
А FPGA - это промежуточная стадия перед ASIC-ом.
Николай.
Цитата(Ynicky @ Oct 24 2009, 00:47)

А FPGA - это промежуточная стадия перед ASIC-ом.
Значит все-таки для ASIC. Для FPGA и 8КБ памяти м/б максимумом (для кода+данные).
flipflop
Dec 5 2009, 14:19
Кто-нибудь реализовал MMU? Поделитесь опытом.
Не совсем понятно, как его реализовать таким образом, чтобы чтение и запись из памяти проходило за один такт (не хочется отходить от классической схемы 5-6 ступенчатого конвейера).
Цитата(flipflop @ Dec 5 2009, 17:19)

Кто-нибудь реализовал MMU? Поделитесь опытом.
Не совсем понятно, как его реализовать таким образом, чтобы чтение и запись из памяти проходило за один такт (не хочется отходить от классической схемы 5-6 ступенчатого конвейера).
А что значит за один такт?
Если обращение происходит к внутренней памяти малого объема (например к встроенной в FPGA)
или к кеш первого уровня (также малого объема) то это можно сделать и за один такт.
Если Вам надо обратиться к дальней памяти или кеш второго уровня (например для заполнения кеш первого уровня),
то в этом случае нужна остановка процессора (stall). А количество тактов в конвейере тут не причем.
Николай.
flipflop
Dec 6 2009, 10:27
Цитата(Ynicky @ Dec 6 2009, 12:41)

А что значит за один такт?
Если обращение происходит к внутренней памяти малого объема (например к встроенной в FPGA)
или к кеш первого уровня (также малого объема) то это можно сделать и за один такт.
Если Вам надо обратиться к дальней памяти или кеш второго уровня (например для заполнения кеш первого уровня),
то в этом случае нужна остановка процессора (stall). А количество тактов в конвейере тут не причем.
Николай.
Это понятно. Рассматриваем только кэш первого уровня: все равно, получается так, что выборка команды и доступ к памяти данных тянет за собой всю производительность.
У меня 32-разрядный RISC (load/store) процессор, целевая микросхема Spartan-3AN (XC3S700AN-FG484):
1) Какую память использовать для кэша: блочную или распределенную?
2) Какой уровень ассоциативности/размер тэга оптимален?
3) Есть ли смысл (с точки зрения производительности) разбить выборку команды и доступ к памяти на 2 ступени? Выигрыш в частоте вполне реален, но конвейер становиться на 2 ступени длиннее, причем одна из них всегда работает в холостую.
Цитата(flipflop @ Dec 6 2009, 13:27)

3) Есть ли смысл (с точки зрения производительности) разбить выборку команды и доступ к памяти на 2 ступени? Выигрыш в частоте вполне реален, но конвейер становиться на 2 ступени длиннее, причем одна из них всегда работает в холостую.
В моем последнем процессоре как раз это и используется.
Команды ветвления при этом имеют два слота задержки, но я на это пошел,
так как только используя слоты можно не потерять в производительности.
Только приходится критичный код править в ассемблере, т.е. переставлять команды,
т.к. компилятор не может это сделать сам.
Еще я сделал флаг в командах ветвления для аннулирования слотов задержки.
Это позволяет немного сократить код (не надо в пустые слоты добавлять nop).
Но этим я пользуюсь редко.
Так же и с данными. Если результат загрузки регистра не используется в следующих
командах, то процессор не останавливается.
Но при этом также, в большинстве случаев, требуется правка кода вручную.
Что касается первых двух пунктов, то оптимальный результат может дать только перебор различных комбинаций.
Николай.
flipflop
Dec 6 2009, 12:54
Цитата(Ynicky @ Dec 6 2009, 15:45)

В моем последнем процессоре как раз это и используется.
Команды ветвления при этом имеют два слота задержки, но я на это пошел,
так как только используя слоты можно не потерять в производительности.
Только приходится критичный код править в ассемблере, т.е. переставлять команды,
т.к. компилятор не может это сделать сам.
...
Что касается первых двух пунктов, то оптимальный результат может дать только перебор различных комбинаций.
Николай.
Ясно, значит я все правильно понимаю и проблемы вполне реальные .
Если не секрет, какой частоты и на каком кристалле вам удалось добиться?
Цитата(flipflop @ Dec 6 2009, 15:54)

Ясно, значит я все правильно понимаю и проблемы вполне реальные .
Если не секрет, какой частоты и на каком кристалле вам удалось добиться?
В ASIC-е на 0,18 - 250 МГц.
Но там много мультимедийных инструкций, тормозящих процессор.
Николай.
flipflop
Dec 6 2009, 13:21
Цитата(Ynicky @ Dec 6 2009, 16:06)

В ASIC-е на 0,18 - 250 МГц.
Но там много мультимедийных инструкций, тормозящих процессор.
Николай.
Ого, а где вы их производите?
Цитата(flipflop @ Dec 6 2009, 16:21)

Ого, а где вы их производите?
Ну не в России же.
Николай.
Ynicky
May 23 2010, 16:56
Перешел в тему "Свои процессоры" из "Цифровой видеокамеры".
Предлагается всем поучаствовать в обсуждении открытого RISC процессора
с большим регистровым файлом. Выкладываю краткое описание процессора.
Пока не охвачена тема доступа к специализированным регистрам, т.е
формат команд для доступа к ним. Какие еще нужны (frame pointer, variable pointer)?
По мере устаканивания архитектуры и системы команд, буду
выкладывать проект на VHDL.
Николай.
Я бы упростил систему команд, все-таки большинству ASIC недоступен, и упор лучше делать на достижение приемлемых характеристик в FPGA.
В "своих процессорах" до сих пор самой сложной задачей являлся софт, так что конкретно обсудить архитектуру лучше после первых версий компилятора с "ассемблерного Си" (начал делать).
Мой ник лучше убрать из титульного листа, тк не автор данной архитектуры, и VHDL не знаю. У меня архитектура другая - нет типов, нет флагов АЛУ, и тд.
Ynicky
May 25 2010, 05:43
Я как раз и собираюсь "пощупать" процессор в FPGA.
Хотя это будет Virtex5 (на работе отдаю новую схему пп для разводки). Туда много чего влезет.
Планирую одну такую платку взять домой на время.
Но можно и попроще.
А как можно сделать if...else без флагов?
Можно, конечно, сделать команды "branch" (BR) без CMP так:
BR if RD = 0, RD /= 0, RD < 0, RD >= 0 и т.д.
Хотя это теже флаги без сохранения в регистре.
Но при этом смещение будет в 12 разрядов или в 32 разряда с предварительной командой IMM.
Николай.
Цитата(Ynicky @ May 24 2010, 02:26)

Предлагается всем поучаствовать в обсуждении RISC процессора с большим регистровым файлом.
1. RISC неэффективно расходует память программ
2. Большой регистровый файл - значит, контекст сохранять долго. Значит, на прерывания медленно будет реагировать.
А кто компилятор будет делать под такую архитектуру? Или предполагается его на ассемблере мутузить?
Цитата(Ynicky @ May 25 2010, 09:43)

А как можно сделать if...else без флагов?
Пример бесфлагового процессора - NIOS II, даже переноса нет.
Цитата(=AK= @ May 25 2010, 11:26)

Большой регистровый файл - значит, контекст сохранять долго. Значит, на прерывания медленно будет реагировать.
А зачем сохранять контекст? Все переменные подпрограммы прерывания - статические. Если нужна реентрабельность - свой собственный статический стек.
Цитата
А кто компилятор будет делать под такую архитектуру? Или предполагается его на ассемблере мутузить?
Для начала - "ассемблерное" подмножество Си (см пример "N-ферзей" в предыдущей ветке), потом расширяем компилируемое подмножество.
Ynicky
May 25 2010, 11:12
Цитата(Leka @ May 25 2010, 12:01)

Пример бесфлагового процессора - NIOS II, даже переноса нет.
Мой опыт в проектировании процессоров говорит, что надо начинать проверку с условных команд
ветвления (BR) и конвейера обработки данных. Так что желательно сейчас сосредоточиться
на этих командах. Если бы места в коде команд хватило бы на 2 регистра и смещение, то я бы
сделал команды условных ветвлений как в NIOSII и MIPS32.
Николай.
Ynicky
May 25 2010, 15:21
Начал делать АЛУ и сразу возникла мысль: в операциях формата RI
поменять местами RD и RS. Тогда получим:
RR: RD = RD opalu RS (пример на ассемблере: add rd,rs)
RI: RD = RS opalu IMM (пример на ассемблере: addi rd,rs,imm)
При этом в формате RI регистр-источник и регистр-приемник могут быть разными.
To Leka: это не усложнит компилятор?
Николай.
Цитата(Ynicky @ May 25 2010, 19:21)

это не усложнит компилятор?
Не усложнит.
Ynicky
May 25 2010, 18:23
Подкорректировал описание.
Проблема вот в чем.
На уровне "ассемблерного" Си нет оптимизации, поэтому для 2х- и 3х- операндных архитектур запись одного и того-же алгоритма д/б различной для достижения оптимума по объему кода и быстродействию.
На примере кода N-ферзей.
Если для 3х-операндной архитектуры оптимально использование индексных массивов "arow[pos]", "arow[pos1]", и тд, то для 2х-операндной архитектуры оптимальнее будет использование модифицируемых указателей "*arow", "*arow1", и тд. Тк одной 3х-операндной инструкции:
arow[pos1] = temp;
непосредственно соответствуют три 2х-операндные инструкции:
tmp = arow;
tmp += pos1;
*tmp = temp;
Ветвления:
"if( pos != N )..." - одна бесфлаговая 3х-операндная инструкция, или
"if( pos - N != 0 )..." - две флаговые 2х-операндные инструкции (сравнение + ветвление), или
"if( tmp=pos, tmp -= N, tmp != 0 )..." - три бесфлаговые 2х-операндные инструкции.
Пришел к выводу, что используемое "ассемблерное" подмножество Си д/б жестко привязано к целевой архитектуре, те все преобразования выражений вынести из уровня компиляции:
"ассемблерное" подмножество Си --> машинные коды,
на уровень компиляции:
к-л подмножество Си --> "ассемблерное" подмножество Си.
На код самого компилятора ("ассемблерное" подмножество Си --> машинные коды) это слабо повлияет, тк все особенности архитектуры предполагаю вынести во внешние таблицы(файлы) - делаю универсальным, чтобы и для своих процессоров использовать.
Цитата(Leka @ May 25 2010, 17:31)

А зачем сохранять контекст? Все переменные подпрограммы прерывания - статические. Если нужна реентрабельность - свой собственный статический стек.
Для того, чтобы обработку прерывания делать на С. И для того, чтобы более высокоприоритетное прерывание могло прервать низкоприоритетное (это не совсем реентерабельность).
А если свой собственный стек - что в нем сохранять-то? Все регистры - слишком долго, веть их же много. А если запретить использовать все, то на фиг их надо было делать много?
Две страницы регистров - отдельно для "нормальной" работы, отдельно для прерываний - выглядит более прогрессивным решением, чем просто много регистров. Еще больше страниц регистров, которые бы образовали стековую структуру, выглядит еще более заманчивым. А на каждой странице много регистров не нужно.
Цитата(Leka @ May 25 2010, 17:31)

см пример "N-ферзей" в предыдущей ветке
Ссылочку приведите
Если регистры не используются одновременно разными подпрограммами, то сохранять их не нужно. Если регистров много - их можно без конфликтов распределить по подпрограммам, и сохранять ничего не придется.
Прерывания в процессоре с 1К регистров у меня реализованы, пишутся на "автокоде"(
http://electronix.ru/forum/index.php?showt...mp;#entry605442 ), никаких потерь на входе/выходе.
"Ассемблерный" Си для 3х-операндной архитектуры:
http://electronix.ru/forum/index.php?showt...st&p=760789
Цитата(Leka @ May 26 2010, 17:58)

Если регистры не используются одновременно разными подпрограммами, то сохранять их не нужно.
Если вы пишете на ассемблере и вручную распределяете регистры и задаете их использование, то не нужно. Да, еще одно условие: вы не используете подпрограмм. Вся ваша программа должна быть написана как линейный "спагетти" код на ассемблере, это обязательное условие, чтобы не сохранять регистры.
Из:
Код
queens( int N ){
int
count,
arow[20],
...
автоматически создается:
Код
int
queens_return;
queens_N;
queens_count,
queens_arow[20],
...
queens(){
...
с соответствующим переименованием всех переменных.
Все переменные стали глобальными, вызов "cnt=queens(8);" преобразуется в:
Код
queens_N=8;
queens();
cnt=queens_return;
И так для всех подпрограмм.
Если не хватит ~1К адресного пространства для всех таких переменных, тогда только и придется организовывать программное сохранение/восстановление контеста, и тп - для некритичного кода. И вовсе не обязательно сохранять/восстанвливать весь регистровый файл. Имхо, очень удобно для небольших систем без монстроидальных ОС.
Цитата(Leka @ May 26 2010, 22:06)

Все переменные стали глобальными,
...
И так для всех подпрограмм.
Я в одной подпрограмме вызову другую, а она пропишет свои временные переменные поверх задействованных. Вот и придется вам весь код раскатать в плоский "блин", без подпрограмм (функций). Или же строго следить за "уровнем": main может использовать одни переменные, подпрограммы первого уровня (их можно вызывать только из main) - другие, подпрограммы второго уровня - свой набор, и т.п. Маразм, короче. Архаическое программирование в стиле старых PIC-ов на ассемблере.
А нету временных переменных. Как после:
#define int static int
и тп.
Проверено на практике на "автокоде" - подпрограммы из подпрограмм вызываются без к-л ограничений, прерывания тоже работают.
Ynicky
May 26 2010, 17:40
Цитата(=AK= @ May 26 2010, 16:46)

Я в одной подпрограмме вызову другую, а она пропишет свои временные переменные поверх задействованных. Вот и придется вам весь код раскатать в плоский "блин", без подпрограмм (функций).
Может я, конечно, чего не понимаю.
Я пока не задавал глупых вопросов про служебные регистры, потому что думал рано. Но их в систему команд все равно придется вводить. К ним я планировал добавить "frame pointer" или как его там назвать. Компилятор знает сколько регистров надо зарезервировать для подпрограммы. При вызове подпрограммы во "frame pointer" записывается это значение (или добавляется к его содержимому). А дальше адрес регистров вычисляется в аппаратуре прибавлением текущего адреса к "frame pointer".
При выходе из подпрограммы - вычитать это значение.
Но что делать, если "frame pointer" перешагнет через максимальное число регистров? Тут я могу завести это событие на NMI или вызвать TRAP. Поправьте, если что-то недопонял.
Николай.
Цитата(Ynicky @ May 26 2010, 21:40)

Я пока не задавал глупых вопросов про служебные регистры,
потому что думал рано. Но их в систему команд все равно
придется вводить. К ним я планировал добавить "frame pointer"
или как его там назвать...
Тогда регистры придется делить на локальные и глобальные, и делать мультиплексор(или что другое) на входе адреса. И сразу делать 3х-операндную архитектуру, тк для 2х-операндной такое не имеет смысла, имхо.
Как обращаться к служебным регистрам из Си - еще не решил, важно обеспечить простоту отладки "ассемблерного" Си-кода на ПК со стандартным Си-компилятором (сам использую открытый Tiny C Compiler). Предлагайте варианты.
http://ru.wikipedia.org/wiki/Tiny_C_Compiler
Гораздо проще придумать свой язык и компилятор к нему, чем делать компилятор к Си.
Масса вопросов типа:
Код
main(){ a() || b() && c(); }
В каком порядке будут вызываться функции a(), b(), c(), и почему?
Вся система команд с учетом архитектуры будет описываться в отдельном файле, в таком примерно формате:
Код
dddddddddddd += ssssssssssss
001 01000 dddddddddddd ssssssssssss
dddddddddddd += kkkkkkkkkkkk
010 01000 kkkkkkkkkkkk dddddddddddd
dddddddddddd += hhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkk
000 00101 xxxxhhhhhhhh hhhhhhhhhhhh
010 01000 kkkkkkkkkkkk dddddddddddd
* ssssssssssss = dddddddddddd
011 xx110 dddddddddddd ssssssssssss
dddddddddddd = * ssssssssssss
011 xx010 dddddddddddd ssssssssssss
dddddddddddd < ssssssssssss
001 00110 dddddddddddd ssssssssssss
110 x0100 pppppppppppp pppppppppppp
...
Это избавит от необходимости переделывать компилятор с "ассемблерного" Си при изменениях в архитектуре (система и разрядность команд, число операндов, регистров, ...).
Цитата(Leka @ May 26 2010, 22:41)

А нету временных переменных.
Насколько мне известно, типичная программа на С в основном использует как раз таки временные переменные. Глобальных в программах немного, зато чуть не в каждой функции - несколько локальных: счетчик цикла, промежуточные результаты, и т.п. Вы предлагаете процессор, при использовании которого временных переменных вообще нет, все они сделаны глобальными. Соответственно, программы потребуют в несколько раз больше памяти данных. То есть, мало того, что ваш процессор неэффективен по использованию памяти программ (поскольку RISC), он, оказывается, и память данных (регистры) транжирит. По-моему, от таких архитектур давным-давно отказались, как от неэффективных.
Я, кстати, свои первые программы писал для машины с 45-разрядными словами и 3-адресными командами. БЭСМ-4 называлась, чудо техники начала 60-х. Дежавю.
Ynicky
May 27 2010, 09:21
Цитата(Leka @ May 26 2010, 23:59)

Тогда регистры придется делить на локальные и глобальные, и делать мультиплексор(или что другое) на входе адреса. И сразу делать 3х-операндную архитектуру, тк для 2х-операндной такое не имеет смысла, имхо.
Как обращаться к служебным регистрам из Си - еще не решил, важно обеспечить простоту отладки "ассемблерного" Си-кода на ПК со стандартным Си-компилятором (сам использую открытый Tiny C Compiler). Предлагайте варианты.
Вообще то лучше сделать служебные регистры в виде сопроцессора как в MIPS32,
и доступ к ним организовать через команды mtcp (move to coprocessor) и mfcp (move from coprocessor).
А что касается компиляторов, то я дело имел только с LCC.
И еще, меня никто не поправил насчет нового формата RI.
Тут я ошибся (сказывается опыт работы с 3-х операндными командами).
Для регистров есть только одно поле в команде, так что 2 регистра и смещение не могут быть
в одной команде 2-х операндных инструкций.
Описание нужно вернуть к версии 1.0.0.
Николай.
Цитата(=AK= @ May 27 2010, 12:59)

ваш процессор неэффективен по использованию памяти программ (поскольку RISC)
Вы, наверно, имеете в виду процессоры с фиксированной длиной команд, а не RISC.
Есть RISC-и с переменной длиной команд, где код используется эффективно.
Но это (фиксированная длина команд) окупается простотой реализации.
Николай.
Цитата(=AK= @ May 27 2010, 12:59)

...Соответственно, программы потребуют в несколько раз больше памяти данных.
На проценты, а не в разы, тк память данных - не только простые переменные и указатели, но и массивы, и в большинстве полезных программ основной объем данных приходится на массивы. А массивы хранятся не в регистровом файле.
Цитата
То есть, мало того, что ваш процессор неэффективен по использованию памяти программ (поскольку RISC), он, оказывается, и память данных (регистры) транжирит. По-моему, от таких архитектур давным-давно отказались, как от неэффективных.
В ветке, где приводил пример программы N-ферзей на автокоде, предлагал сравнить эффективность архитектур на разных софт-процессорах - никто не откликнулся.
Эффективность использования памяти программ гораздо сильнее зависит от качества кода, чем от разрядности команд. Пример - Tiny C Compiler, 100Кб кода против десятков Мб от других разработчиков.
Удачное разбиение кода на маленькие подпрограммы - самый эффективный способ сжать код, поэтому важны малые издержки на вызовы пп. Например, у меня команда call совмещена с пересылкой регистр-регистр, а ret - c любой операцией алу:
f(&a); - одна инструкция на вызов пп с передачей аргумента,
f(int *a){ return *a >>= 8; } - одна инструкция на всю пп.
Цитата(Leka @ May 27 2010, 19:01)

сравнить эффективность архитектур
Есть специальная контора, которая сравнивает -
http://www.eembc.org/ Бенчмарки, приведенные в
презентациях PTSC, меня лично вполне убедили, что с большим отрывом лидирует стековая архитектура
Нажмите для просмотра прикрепленного файла
Презентации - неинтересно.
Первая попавшаяся нерекламная ссылка по IGNITE:
http://java.epicentertech.com/Archive_Embe...sor%20-%20Java/
Ynicky
May 30 2010, 07:15
Ну вот, выкладываю первую версию процессора.
Проект сделан в ACTIVE-HDL 8.2. На нем проще моделировать.
На конечном этапе сделаю в ISE для Xilinx и в QuartusII для альтеры.
Память программ и регистровый файл сделал пока поведенческими для быстроты моделирования.
Программу проверки пишу в кодах (файл prg.txt). Оказалось не очень нудно,
т.к. простые форматы команд. Отладил почти все условия ветвления в командах BRcc.
АЛУ полностью пока не проверял (только частично в тестах BRcc).
To Leka: В принципе, можно уже моделировать программу N-ферзей.
Хорошо бы компилятор мог на выходе записывать текстовый файл (как prg.txt).
Но это не обязательно. Я, наверно, смогу сам преобразовать из других форматов.
И еще: В директории doc находится новая версия описания RF32.
Николай.
Цитата(Ynicky @ May 30 2010, 02:15)

Ну вот, выкладываю первую версию процессора.
Спасибо конечно, но вот те кто не пользуются альедеком должны вытаскивать структуру из скомпилированного bdf файла? Это я к тому что структурная и функциональная схема не помешали бы, также как и описание портов ввода/вывода процессора %)
На этой неделе постараюсь выложить первую версию компилятора. Отвлекают вопросы методического характера, например: Си ориентирован на явное использование указателей --> получается неудобно для архитектуры с большим регистровым файлом, тк указатели нельзя применять к регистрам...
Цитата(Ynicky @ May 30 2010, 11:15)

Память программ и регистровый файл
Нужна еще память данных для массивов, косвенно адресуемых LOAD/STORE.
Цитата(Leka @ Jun 1 2010, 02:17)

Нужна еще память данных для массивов, косвенно адресуемых LOAD/STORE.
На днях сделаю.
Цитата(des00 @ May 30 2010, 14:32)

Спасибо конечно, но вот те кто не пользуются альедеком должны вытаскивать структуру из скомпилированного bdf файла? Это я к тому что структурная и функциональная схема не помешали бы, также как и описание портов ввода/вывода процессора %)
Учту.
Чтобы проще было вылавливать ошибки в компиляторе с "ассемблерного" подмножества Си, решил добавить промежуточный уровень "машинного" подмножества Си:
... --> "ассемблерный" Си --> "машинный" Си --> машинные коды.
Пример: N-ферзей на "машинном" Си для 3х-операндной архитектуры без типов,
main() - тестовая программа на Си - игноируется компилятором в машинные коды по пробелу в начале строки.
CODE
R1;
R2;
R3[20];
R4[20];
R5[20];
R6[20];
R7;
R8;
R9;
R10;
R11;
R12;
R13;
R14;
queens(){
L1:R2=0;
L2:R12=R1&1;
L3:R13=1<<R1;
L4:R9=R13-1;
L5:R13=R1>>1;
L6:R7=R9>>R13;
L7:R3[1]=0;
L8:R4[1]=0;
L9:R5[1]=0;
L10:R6[1]=R7;
L11:R10=1;
L12:if(R7==0)goto L46;
L13:R13=0-R7;
L14:R8=R7&R13;
L15:R13=-1-R8;
L16:R7=R7&R13;
L17:if(R10!=R12)goto L20;
L18:if(R7!=0)goto L20;
L19:R2=R2<<1;
L20:if(R10==R1)goto L44;
L21:R11=R10+1;
L22:R6[R10]=R7;
L23:R13=R3[R10];
L24:R13=R13|R8;
L25:R3[R11]=R13;
L26:R13=R4[R10];
L27:R13=R13|R8;
L28:R13=R13<<1;
L29:R4[R11]=R13;
L30:R13=R5[R10];
L31:R13=R13|R8;
L32:R13=R13>>1;
L33:R5[R11]=R13;
L34:R13=R3[R11];
L35:R14=R4[R11];
L36:R13=R13|R14;
L37:R14=R5[R11];
L38:R13=R13|R14;
L39:R13=-1-R13;
L40:R13=R13&R9;
L41:R7=R13;
L42:R10=R11;
L43:goto L45;
L44:R2=R2+1;
L45:goto L48;
L46:R10=R10-1;
L47:R7=R6[R10];
L48:if(R10!=0)goto L12;
L49:if(R12!=0)goto L51;
L50:R2=R2<<1;
L51:return(R2);
}
main(){
int n,cnt;
for(n = 1; n < 15; n = n + 1){
R1= n;
queens();
cnt = R2;
printf("queens(%d)=%d \n", n, cnt);
}
}
А что означают выражения?:
R6[R10]=R7;
R13=R3[R10];
Николай.
Косвенную адресацию памяти
R6[R10]=R7; --> STORE R7,(R6+R10)
R13=R3[R10]; --> LOAD R13,(R3+R10)
Для 2х-операндной архитектуры:
R6[R10]=R7; -->
MOV R0,R6
ADD R0,R10
ST R7,(R0)
R13=R3[R10]; -->
MOV R0,R3
ADD R0,R10
LD R13,(R0)
или переписать "ассемблерный" код под 2х-операндную архитектуру с использованием указателей R6 R3 вместо пар база-индекс:
*R6=R7;
R13=*R3;
и учесть такие мнемоники в трансляторе в машинные коды.
Вариант N-ферзей с указателями на "ассемблерном" Си (кроме main(){}).
CODE
queens( int N ){
int
count,
arow[20],
aleft[20],
aright[20],
aposs[20],
poss,
place,
val,
pos,
pos1,
N1,
temp,
temp1,
*prow,
*pleft,
*pright,
*pposs,
*prow1,
*pleft1,
*pright1,
*pposs1;
count = 0;
N1= N & 1;
temp = 1 << N;
val = temp - 1;
temp = N >> 1;
poss = val >> temp;
pos = 1;
//
prow=arow+1;
pleft=aleft+1;
pright=aright+1;
pposs=aposs+1;
//arow[1] = 0;
//aleft[1] = 0;
//aright[1] = 0;
//aposs[1] = poss;
*prow = 0;
*pleft = 0;
*pright = 0;
*pposs = poss;
do{
if( poss != 0 ){
temp = -poss;
place = poss & temp;
temp = ~place;
poss = poss & temp;
if( pos == N1 && poss == 0 )
count = count << 1;
if( pos != N ){
pos1 = pos + 1;
//
prow1 = prow+1;
pleft1 = pleft+1;
pright1 = pright+1;
pposs1 = pposs+1;
//aposs[pos] = poss;
*pposs = poss;
//temp = arow[pos];
temp = *prow;
temp = temp | place;
//arow[pos1] = temp;
*prow1 = temp;
//temp = aleft[pos];
temp = *pleft;
temp = temp | place;
temp = temp << 1;
//aleft[pos1] = temp;
*pleft1 = temp;
//temp = aright[pos];
temp = *pright;
temp = temp | place;
temp = temp >> 1;
//aright[pos1] = temp;
*pright1 = temp;
//temp = arow[pos1];
temp = *prow1;
//temp1 = aleft[pos1];
temp1 = *pleft1;
temp = temp | temp1;
//temp1 = aright[pos1];
temp1 = *pright1;
temp = temp | temp1;
temp = ~temp;
temp = temp & val;
poss = temp;
pos = pos1;
//
prow += 1;
pleft += 1;
pright += 1;
pposs += 1;
}else
count = count + 1;
}else{
pos = pos - 1;
//
prow -= 1;
pleft -= 1;
pright -= 1;
pposs -= 1;
//poss = aposs[pos];
poss = *pposs;
}
}while( pos != 0 );
if( N1 == 0 )
count = count << 1;
return count;
}
main(){
int N;
for(N = 1; N < 15; N = N + 1){
printf("queens(%d)=%d \n", N, queens(N));
}
}
Закончил "в черне" процессор.
Вынес память программ и данных из ядра.
Дополнил системной шиной AMBA AHB и отладочными блоками для
работы через JTAG.
Теперь только тесты, тесты, правка и еще раз тесты.
Описание дополню в выходные.
Николай.
Написал компилятор "ассемблерный" Си --> "машинный" Си, причешу - выложу (либо завтра, если успею, либо в понедельник).
Надо-бы побольше программ на "ассемблерном" Си для тестирования (как компилятора, так и процессора).
"Машинный" Си --> машинные коды на этой неделе уже не успею, но эта задачка заметно проще.
Цитата(Ynicky @ Jun 2 2010, 23:34)

Описание дополню в выходные.
Дополнил описание.
Первая версия компилятора "ассемблерный" Си --> "машинный" Си.
Поддерживаются: int, if-else, do-while, блоки { }, см. примеры N-ферзей.
В текущей версии не поддерживаются вложенные круглые скобки и круглые скобки в выражениях.
Запуск из командной строки:
a2m < входной_файл > входной_файл
Например(см. a.bat):
a2m < q3.c > q3..c
Выходные (и входные) файлы можно проверить любым Си-компилятором, например, Tiny C Compiler в скриптовом режиме:
c:\tcc\tcc.exe -run q3..c
При установленном TinyCC:
a q3
Под "ассемблерным" подмножеством Си подразумевается отсутствие длинных арифметических и логических выражений, допускаются только 3х-операндные выражения(с учетом адреса перехода), которые м/б непосредственно преобразованы в машинные коды.
По поводу FP(указателя на кадр) - как в подпрограммах получать доступ к глобальным переменным в регистровом файле?
У меня указатель на область локальных переменных аппаратно "разворачивал" нумерацию регистров, так что R(0), R(1), R(2), ... в подпрограмме любой вложенности указывали на глобальные переменные, а R(-1), R(-2), R(-3) - на локальные, или наоборот. Например, для 8 регистров:
Код
R0 R1 R2 R3 R4 R5 R6 R7 - int R0;
R0 R7 R6 R5 R4 R3 R2 R1 - main(){ int R7, R6, R5; f1( R5 ); }
R0 R1 R2 R7 R6 R5 R4 R3 - f1( int R7 ){ int R6, R5; f2( R5 ); }
R0 R1 R2 R3 R4 R7 R6 R5 - f2( int R7 ){ int R6, R5; ... }
С программной точки зрения подобная перенумерация регистров очень удобна. Проверил и в железе, и в ассемблере. Единственный недостаток - лишняя ступень логики.
Для просмотра полной версии этой страницы, пожалуйста,
пройдите по ссылке.