Непонятное поведение флага CF

Допустим, есть кусок кода:

mov al, 12
mov bl, 4
sub al, bl

В данном случае флаг CF=0, но на мой взгляд он должен быть равен 1, так как операция вычитания реализована на операции сложения и процессор не знает, что мы ему подаем на вход, будь то знаковое или беззнаковое номера, он просто делает свою работу.

То есть приведенный выше код эквивалентен следующему:

Введите значение 12 в регистр al, т.е. 0000 1100

Введите значение 4 в регистр bl, т.е. 0000 0100

Далее идет операция вычитания, так как первый операнд положительный, преобразований в дополнительный код нет. Так как второй тоже положителен, преобразований тоже нет, но так как выполняется операция вычитания, то второй операнд транслируется в дополнительный код и процессор выполняет операцию сложения (вычитание реализуется сложением), то есть:

12: 0000 1100
-4: 1111 1100



12 - 4 = 12 + (-4) = 0000 1100 + 1111 1100 = 1 0000 1000

То есть мы получили правильный результат — 8, но CF = 0, если это отладить. Почему это? Тот, что вышел за пределы битовой сетки, помещается в CF, но CF = 0.


person Danny    schedule 20.11.2020    source источник
comment
Вы действительно используете 3 разных ассемблера?   -  person Scott Hunter    schedule 20.11.2020
comment
Ассемблер, язык ассемблера не имеет к этому никакого отношения, если только он не содержит ошибок и не создает неправильный машинный код.   -  person old_timer    schedule 20.11.2020
comment
en.wikipedia.org/wiki/Carry_flag флаг переноса инвертирован в заимствование для x86   -  person old_timer    schedule 20.11.2020


Ответы (2)


Читая руководство, флаг CF указывает на беззнаковое переполнение. Однако это не означает переполнения вдобавок.

Вы предполагаете, что установленный бит 8 указывает на переполнение, однако для вычитания это противоположный случай. Если бы он не был установлен, это означало бы, что заимствование произошло с бита вверх.

Если мы заменим 4 на 16, чтобы мы увидели переполнение:

00001100b - 00010000b = 00001100b + 11110000b = 11111100b = 252d

Вы можете видеть, что нет переноса до 8-го бита, хотя произошло беззнаковое переполнение. Флаг CF не является результатом добавления отрицательного значения. Это просто указывает на переполнение при вычитании. В данном случае это означает, что он противоположен этому биту переполнения.


Различные архитектуры обрабатывают флаг переноса по-разному. Некоторые используют его, чтобы указать, что произошло заимствование, в то время как другие используют его для представления того бита, который будет заимствован. У них часто есть инструкция (sbb в x86), которая позволяет создавать цепочки с использованием флагов как части ввода.

person Thomas Jager    schedule 20.11.2020

Из начальной школы a - b = a + (-b). От введения к программированию -a = ~a + 1. Итак, в логике

        1
 00001100
+11111011
=========

и закончи это

111111111
 00001100
+11111011
=========
 00001000

Затем возникает вопрос архитектуры: по пути вы инвертируете перенос и второй операнд для вычитания. Некоторые архитектуры инвертируют выполнение, чтобы оно представляло флаг заимствования, некоторые оставляют его нетронутым как флаг не заимствования. 12 - 4 не заимствованы, поэтому мы можем предсказать заимствование, но для этого нужно посмотреть архитектурные документы. В частности, такие вещи, как меньше и больше, и какие флаги проверяются...

Кроме того, стороны, не являющиеся переносом и переносом msbit, одинаковы, поэтому в этом случае нет переполнения со знаком (+8 может быть представлено 8 битами).

Википедия указывает, что x86 использует флаг заимствования

Первый использует бит как флаг заимствования, устанавливая его, если a‹b при вычислении a−b, и заимствование должно быть выполнено.

 00111
  0010
+ 1011
======
  1110

2 - 4, 2 ‹ 4, перенос равен 0, поэтому установите флаг заимствования

Это плюс ваше свидетельство указывает на то, что x86 инвертирует бит переноса на выходе из вычитания, чтобы указать заимствование.

Здесь с вашим экспериментом все выглядит хорошо.

Обратите внимание, что красота дополнения до двух состоит в том, что операции сложения и вычитания работают с одной и той же логикой, и ему не нужно отличать знаковое от беззнакового. Умножение и деление при использовании нечетных размеров (х бит, умноженное на х бит = х + х бит. Х + х бит, разделенное на х бит) действительно заботятся о знаковых и беззнаковых. Они часто добавляют подписанное переполнение вместе с неподписанным переполнением, чтобы помочь с подписанными и неподписанными условными ветвями, которые очень заботятся о подписанных и неподписанных, подписанная условная ветвь и неподписанные одного и того же вкуса не являются одними и теми же флагами большую часть времени.

Редактировать

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

Моя старая печатная копия руководства Intel 8088 говорит.

Если CF (флаг переноса) установлен, имел место перенос или заимствование старшего бита результата (8- или 16-битного). Флаг используется инструкциями, которые добавляют и вычитают многобайтовые числа. Инструкции ротации также могут изолировать бит в памяти или регистре, поместив его во флаг переноса.

Для операции добавления cdf устанавливается, если имел место перенос (1). Для подоперации, как в этом случае, устанавливается cf, если имело место заимствование. Независимо от того, как вы реализуете операцию в логике, это определение флага, поэтому, если вы строите логику вычитания (маловероятно), вы все равно что-то инвертируете, если вы используете логику добавления для выполнения вычитания, вы в конечном итоге инвертируете выполнение. (в обоих случаях вы в конечном итоге инвертируете выполнение).

Использование 4 бит, потому что все масштабируется до n бит.

В логике вы бы сделали что-то вроде

result = 0aaaa + 0iiii + 1;

or

result = 1aaaa - 0bbbb;

где iiii = ~b;

Возьмем для примера 1-4

Использование сумматора для выполнения вычитания 1 - 4 = 1 + (-4) математика не имеет ничего общего с компьютерами, они старше компьютеров на столетия.

      1
  00001
+ 01011
========

заполните это

  00111
  00001
+ 01011
========
  01101

BIT выполнения равен 0, поэтому флаг переноса = ~ 0 = 1 для обозначения того, что произошло заимствование. Результат 1101 (-3).

Использование вычитания в начальной школе с небольшим поворотом, чтобы не переворачивать операнды и отрицать результат

 10001
-01011
=======

Потому что это текстовое представление будет использовать 2 для представления двоичного числа 10, так как я не могу его уместить.

 10001
-00100
=======

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

Я могу зайти так далеко, не занимая

 10001
-00100
=======
    01

Благодаря вашим вопросам и поискам в Википедии, я теперь знаю, что это называется американским методом.

Мне нужно позаимствовать один из столбца 16s, чтобы сделать столбец 8s равным 2, потому что в столбце четверок есть ноль, нужно продолжать, пока вы не нажмете ненулевое значение, а затем позаимствовать оттуда.

 02001
-00100
=======
    01

И тогда мне приходится заимствовать из столбца восьмерок, чтобы поместить что-то в столбец четверок.

 01201
-00100
=======
    01

Теперь мы можем продолжить

 01201
-00100
=======
 01101

Мы получаем тот же результат, что и при использовании метода сложения, 16-й столбец 1 необходим для правильного заимствования без перестановки операндов, в начальной школе мы делали 1–4, выполняя 4–1, а затем отрицая результат.

  4
- 1
=====
  3

и мы превратим это в -3, потому что как вы можете продемонстрировать заимствование (с помощью американского метода), когда заимствовать нечего?

Таким образом, оба заканчиваются 01101, что является переносом 0 и результатом -3. Но поскольку определение говорит

Если CF (флаг переноса) установлен, то это означает, что произошло... заимствование, старший бит результата

Итак, нам нужно, чтобы флаг переноса был равен 1, поэтому

if(add) cf = carry_out;
if(sub) cf = ~carry_out;

Вы можете реализовать все это самостоятельно в некотором коде C, очень похожем на то, что вы видели бы в логике.

Скажем, 4-битное сложение alu и две возможные формы вычитания, с флагом переноса, представляющим заимствование (если заимствование, то устанавливается).

unsigned int c_flag;
unsigned int n_flag;
unsigned int z_flag;
unsigned int v_flag;
unsigned int alu_add ( unsigned int a, unsigned int b )
{
    unsigned int c;

    c = a + b;
    c_flag = (c>>4)&1;
    c &= 0xF;
    if(c) z_flag = 1; else z_flag = 0;
    n_flag = (c>>3)&1;
    v_flag = 0;
    if((a&8)==(b&8)) if((b&8)==(c&8)) v_flag = 1;
    return(c);
}
unsigned int alu_sub ( unsigned int a, unsigned int b )
{
    unsigned int c;

    b = (~b) & 0xF;
    c = a + b + 1;
    c_flag = ((~c)>>4)&1;
    c &= 0xF;
    if(c) z_flag = 1; else z_flag = 0;
    n_flag = (c>>3)&1;
    v_flag = 0;
    if((a&8)==(b&8)) if((b&8)==(c&8)) v_flag = 1;
    return(c);
}
unsigned int alu_sub_alt ( unsigned int a, unsigned int b )
{
    unsigned int c;

    c = (0x10|a) - b;
    c_flag = ((~c)>>4)&1;
    c &= 0xF;
    if(c) z_flag = 1; else z_flag = 0;
    n_flag = (c>>3)&1;
    v_flag = 0;
    if((a&8)==(b&8)) if((b&8)==(c&8)) v_flag = 1;
    return(c);
}

Подборка тестовых векторов

1 0  1( 1)  0( 0) add =  1( 1) cZnV sub =  1( 1) cZnv sub_alt =  1( 1) cZnV
1 1  1( 1)  1( 1) add =  2( 2) cZnV sub =  0( 0) cznv sub_alt =  0( 0) cznV
1 2  1( 1)  2( 2) add =  3( 3) cZnV sub = 15(-1) CZNv sub_alt = 15(-1) CZNv
1 3  1( 1)  3( 3) add =  4( 4) cZnV sub = 14(-2) CZNv sub_alt = 14(-2) CZNv
1 4  1( 1)  4( 4) add =  5( 5) cZnV sub = 13(-3) CZNv sub_alt = 13(-3) CZNv
1 5  1( 1)  5( 5) add =  6( 6) cZnV sub = 12(-4) CZNv sub_alt = 12(-4) CZNv
1 6  1( 1)  6( 6) add =  7( 7) cZnV sub = 11(-5) CZNv sub_alt = 11(-5) CZNv

Никто из нас здесь не может сказать, что у нас есть доступ к исходному коду реальных чипов Intel в производстве, поскольку мы будем защищены различными способами NDA или контрактами сотрудников и т. д.

Но код для сегодняшних процессоров будет написан на каком-то HDL. Я подозреваю, что Intel не использует Verilog напрямую, а какой-то другой, может быть, собственный язык, который затем компилируется в Verilog, а затем Verilog передается инструменту синтеза, и этот инструмент в конечном итоге решает, как обращаться с вещами.

Вы могли бы сделать что-то вроде этого

assign fadder_out       = { 1'd0,a} + {1'd0,b_not} + 5'b00001;

или что-то вроде этого

assign fadder_out       = { 1'd1,a} - {1'd0,b};

а потом

assign cf = alu_op == add ? fadder_out[4] : 
            alu_op == sub ? ~fadder_out[4] ;

Почему Intel решила представить cf как флаг заимствования, а не как необработанное выполнение? Вероятно, потому что 8080 сделал это, потому что 8008 сделал это, потому что 4004 сделал это. Я подозреваю, что людей в здании в то время уже нет в живых, чтобы спросить, а если и были, то, вероятно, с ними невозможно связаться.

Выбор действительно произвольный, до тех пор, пока ваши условные выражения и вычитание с заимствованием, если они у вас есть, соответствуют одному и тому же выбору, тогда любой случай работает нормально, как мы знаем, потому что некоторый процент процессоров в мире берет необработанные данные. выполнить, и мы можем думать, что это не заимствование (0, если заимствование), остальные называют это заимствованием (1, если заимствование).

Еще во времена 8086, но еще раньше, компоновка/маска делалась вручную на чертежном столе, каждый слой, каждый транзистор рисовались вручную. Реализуете ли вы вычитание в логике с помощью вычитания, делаете ли вы это с помощью сумматора и т. д. Большая причина для CISC до RISC заключалась в том, что вы не реализовали всю эту чертову штуку в необработанных вентилях, вы реализовали модули, которые имели много проводов , и вы кормили это конечным автоматом на основе рома. Инструкции CISC вызывают выполнение смещения в ПЗУ, микрокод, если хотите, или входные данные конечного автомата, затем заставляют вентили переворачиваться здесь и там, вычитание может означать инвертирование операнда b, инвертирование переноса, защелкивание выходного сумматора, защелка инвертированного переноса вне. Добавление может быть не инвертировать операнд b, не инвертировать перенос, защелкнуть выход сумматора, защелкнуть неинвертированное выполнение.

Или они, возможно, реализовали отдельный блок для вычитания из сложения, точно так же, как есть отдельный блок для XOR, OR, AND и т. д. И все они могут быть снабжены операндами a и b в любой форме (инвертированной или нет) потому что их выполнение и нулевые флаги и тому подобное зависят от операции в целом и в случае x86.

Так что мы не знаем. 4004, вероятно, 8008, вероятно, 8080 и, вероятно, некоторые 8086 были нарезаны и отсканированы людьми, которые сделали visual6502, если не другими, и, возможно, вы действительно можете посмотреть на ворота и увидеть, как это было реализовано, если вы хотите знать историю. Вы также можете посмотреть на 8080, 8008 и 4004, чтобы увидеть, были ли у них флаги (скорее всего), и если да, они указали флаг заимствования или нет, и так далее.

Независимо от того, реализовано ли это в логике как добавление или вычитание, если вы определяете cf как 1 для заимствования, вам нужно инвертировать выполнение в cf, если операция является вычитанием, вычитанием с заимствованием или сравнением.

Ваш следующий вопрос, в котором вы исследовали разницу между 4 + (-12) и 4 - 12. В одном используется инструкция добавления, а в другом - инструкция вычитания, как показано выше, если вычесть cf = ~ Carry_out, если используется любая из двух логических моделей выше . Если вместо этого у вас есть результат = 0aaaa - 0bbbb, который приводит к 1 для результата (возможно, австрийский метод), тогда эта логика может передать этот бит. Я не могу это ни визуализировать, ни продемонстрировать.

person old_timer    schedule 20.11.2020