Оригинал взят у
vkorehovisback в Как сделать PCI карту своими руками, Часть 6 (Больше верилога!)
https://github.com/vkorehov/cncfpga2
Дело в том что использовать закрытые коры это не православно.
Поэтому первым делом я решил от нее избавится.
Я решил оставить тестбенч, временные констрейнты в UCF, некоторые настройки компиляции.
Все остальное (что было закрыто) я решил переписать на чистом верилоге!
Начать нужно с секции ввода вывода она скопирована из коры, и не является закрытой (cnc_top.v).
Она содержит, входные буферы и входные триггеры:
это все примитивы библиотеки unisim. Найти таблицы истинности можно в гугле.
Для всех остальных сигналов все делается похожим способом. Иногда вместо IOBUF используется IBUF или ОBUF если соответствующий сигнал только выход или только вход, и т.д.
Клок обрабатывается по особенному:
Далее везде используется CLK
Особенно нужно остановиться на реализации ресета, в коре использовалась стандартная реализация:
Для Xilinx есть более экономичный и элегантный способ:
http://www.eetimes.com/document.asp?doc_id=1278998
Далее уже нужно сделать скелетон нашей реализации PCI (pci.v)
Заметьте что некоторые выходы помещены в регистры! это важно!.
когда я смотрел на реализацию в старой коре, они используют триггеры как для входов так для выходов, так и для сигналов включения выходов (OE_...)
Далее временная спецификация (сохранена от старой коры):
Первым делом они назначают выводам различные временные группы.
названия будут: PCI_PADS_C, PCI_PADS_G, PCI_PADS_B и т.д.
Далее специальные названия для триггеров входных данных+комманд и выходных данных (команды я не использую)
обратите в нимание, что при работе с шинами, в верилоге у нас выход с регистром называется AD_O, в процессе компиляции он разбивается на провода и соответственно в файле с константами его нужно указывать как AD_O_xxx
Если что-то удалилось из дизайна в процессе оптимиазации, то также эти регистры нельзя указать в файле с константами и это будет вызывать ошибки.
Следует отметить что поскольку некоторые сигналы PCI коммутируются одновременно (TRDY и STOP например), то компилятор будет считать их эквивалентными и пытаться убрать второй регистр, это крайне нежелательно, по причинам которые обьясню чуть позже, но пока лишь скажу что для того чтобы этого не происходило нужно убрать галку с
Equivalent Register Removal в настройках синтеза.
Далее определяем одни группы через логические операции над другими:
Далее когда названия групп определены, переходим к спецификации констрейнтов оптимизации (скопировано из коры как есть):
Если такой файл попробовать имплементировать, то имплементация выдаст ошибку таймингов.
Для того чтобы тайминги соответствовали спецификации PCI, нужно запихнуть входные триггеры, выходные триггеры а также триггеры Output Enable (OE_) в блоки ввода-вывода (IOB)
Для этого используются вот такие директивы:
После этого все должно скомпилироваться успешно, а в конце маппинга, выдается вот такая вот таблица:
Обратите внимание на INFF, OUTFF и ENFF.
Я использовал ровно такие настройки как и старая кора.
Далее собственно имплементация интерфейса.
я разделил это на три блока:
сигналы PCI и управление шиной:
Ввод данных:
Вывод данных:
как видно я использую два бара, один бар ввода вывода, а другой памяти.
размеры задаются параметрами:
следует иметь ввиду, что размер окна памяти (BAR2_) не может быть меньше 16 адресов (потому как первые 4 бита конфигурационного регистра зарезервивованы) а размер окна I/O не может быть меньше 4 адресов , потому как адресация I/O 8-битная, и только два младших бита зарезервивованы.
Я жестко требую выравнивания памяти при обращении. Byte Enable не использую все должно быть выравнено на 32 бита.
Далее важно проверить соответствие спецификации, конечной Post-Route симуляции!
Например я пытался слишком быстро устанавливать TRDY в результате чего адреса читались как нули (не успевали устаовиться) я это исправил задержав TRDY на один клок, это кстати быстрее оригинальной коры на один клок, у них задерживалось на два клока относительно DEVSEL.
Генерируем файл верилога для конечной симуляции:

это создаст файлы в папке nergen.
Создадим новый .do файл для Modelsim: timesim.do
Создадим новый .f файл для Modelsim: cnc_tb_timesim.f
Запустим симуляцию:
проверим чтобы нигде не было красного (1'bx) что означает неинициализированные данные.
а синее (1'bz) какраз было в так называемых turnaround циклах PCI. естественно нас интересуют:
AD, CBE, TRDY, DEVSEL, STOP, PAR.
да, важно понять что PAR устанавливатся на следующем клоке, т.е. сначала мы пишем данные, а на следующем клоке устанавливаем их parity. и так это сдвинуто на протяжении всей транзакции (если использовать Burst, что мне не нужно):

Далее записываем новую прошивку в устройство, выключаем питание, загружаемся, драйвер показывает новые ресурсы.
проверяем соответствие верилогу:

Дело в том что использовать закрытые коры это не православно.
Поэтому первым делом я решил от нее избавится.
Я решил оставить тестбенч, временные констрейнты в UCF, некоторые настройки компиляции.
Все остальное (что было закрыто) я решил переписать на чистом верилоге!
Начать нужно с секции ввода вывода она скопирована из коры, и не является закрытой (cnc_top.v).
Она содержит, входные буферы и входные триггеры:
module cnc_top(
inout [31:0] AD,
...
...
IOBUF_PCI33_5 XPCI_ADB3 (.O(AD_IN3 ),.IO(AD[3 ]),.I(AD_O3 ),.T(OE_AD_N));
IOBUF_PCI33_5 XPCI_ADB2 (.O(AD_IN2 ),.IO(AD[2 ]),.I(AD_O2 ),.T(OE_AD_N));
IOBUF_PCI33_5 XPCI_ADB1 (.O(AD_IN1 ),.IO(AD[1 ]),.I(AD_O1 ),.T(OE_AD_N));
IOBUF_PCI33_5 XPCI_ADB0 (.O(AD_IN0 ),.IO(AD[0 ]),.I(AD_O0 ),.T(OE_AD_N));
...
FDPE XPCI_ADIQ3 (.Q(AD_I3 ),.D(AD_IN3 ),.C(CLK),.CE(1'b1),.PRE(RST));
FDPE XPCI_ADIQ2 (.Q(AD_I2 ),.D(AD_IN2 ),.C(CLK),.CE(1'b1),.PRE(RST));
FDPE XPCI_ADIQ1 (.Q(AD_I1 ),.D(AD_IN1 ),.C(CLK),.CE(1'b1),.PRE(RST));
FDPE XPCI_ADIQ0 (.Q(AD_I0 ),.D(AD_IN0 ),.C(CLK),.CE(1'b1),.PRE(RST));
...
// instantiate our PCI interface implementation
PCI PCI(
.AD_I({... AD_I3, AD_I2, AD_I1, AD_I0}),
.AD_O({... AD_O3, AD_O2, AD_O1, AD_O0}),
.OE_AD_N(OE_AD_N),
...
это все примитивы библиотеки unisim. Найти таблицы истинности можно в гугле.
Для всех остальных сигналов все делается похожим способом. Иногда вместо IOBUF используется IBUF или ОBUF если соответствующий сигнал только выход или только вход, и т.д.
Клок обрабатывается по особенному:
module cnc_top(
...
input PCLK,
...
);
...
// clock
IBUFG_PCI33_5 XPCI_CKI (.O(NUB),.I(PCLK));
BUFG XPCI_CKA (.O(CLK),.I(NUB));
...
Далее везде используется CLK
Особенно нужно остановиться на реализации ресета, в коре использовалась стандартная реализация:
always @(posedge RST)
begin
something_to_init = 111;
end
Для Xilinx есть более экономичный и элегантный способ:
http://www.eetimes.com/document.asp?doc_id=1278998
module cnc_top(
...
input RST_N,
...
);
IBUF_PCI33_5 XPCI_RST (.O(RST_I),.I(RST_N));
assign RST = ~RST_I;
// make sure that RST is bound to Global Reset Request,
// which will put all state to initial block defined one.
STARTUP_SPARTAN2 SPARTAN2(.GSR(RST));
Далее уже нужно сделать скелетон нашей реализации PCI (pci.v)
module PCI(
input [31:0] AD_I,
output reg [31:0] AD_O,
output reg OE_AD_N,
input [3:0] CBE_I_N,
output [3:0] CBE_O_N,
output OE_CBE_N,
input PAR_I,
output reg PAR_O,
output reg OE_PAR_N,
input FRAME_I_N,
output FRAME_O_N,
output OE_FRAME_N,
input TRDY_I_N,
output reg TRDY_O_N,
output reg OE_TRDY_N,
input IRDY_I_N,
output IRDY_O_N,
output OE_IRDY_N,
input STOP_I_N,
output reg STOP_O_N,
output reg OE_STOP_N,
input DEVSEL_I_N,
output reg DEVSEL_O_N,
output reg OE_DEVSEL_N,
input IDSEL_I,
input PERR_I_N,
output PERR_O_N,
output OE_PERR_N,
input SERR_I_N,
output OE_SERR_N,
output OE_REQ_N,
input GNT_I_N,
input CLK,
output OE_INTA_N,
output reg PING_DONE,
input RST
);
...
Заметьте что некоторые выходы помещены в регистры! это важно!.
когда я смотрел на реализацию в старой коре, они используют триггеры как для входов так для выходов, так и для сигналов включения выходов (OE_...)
Далее временная спецификация (сохранена от старой коры):
Первым делом они назначают выводам различные временные группы.
NET "SERR_N" TNM = PADS:PCI_PADS_C ; NET "PERR_N" TNM = PADS:PCI_PADS_C ; NET "REQ_N" TNM = PADS:PCI_PADS_G ; NET "GNT_N" TNM = PADS:PCI_PADS_G ; NET "FRAME_N" TNM = PADS:PCI_PADS_C ; NET "IRDY_N" TNM = PADS:PCI_PADS_C ; NET "TRDY_N" TNM = PADS:PCI_PADS_C ; NET "DEVSEL_N" TNM = PADS:PCI_PADS_C ; NET "STOP_N" TNM = PADS:PCI_PADS_C ; NET "CBE_N<3>" TNM = PADS:PCI_PADS_B ; NET "CBE_N<2>" TNM = PADS:PCI_PADS_B ; NET "CBE_N<1>" TNM = PADS:PCI_PADS_B ; NET "CBE_N<0>" TNM = PADS:PCI_PADS_B ; NET "PAR" TNM = PADS:PCI_PADS_P ; NET "IDSEL" TNM = PADS:PCI_PADS_C ; NET "INTA_N" TNM = PADS:PCI_PADS_X ; NET "RST_N" TNM = PADS:PCI_PADS_X ; # NET "AD<31>" TNM = PADS:PCI_PADS_D ; NET "AD<30>" TNM = PADS:PCI_PADS_D ; ... NET "AD<0>" TNM = PADS:PCI_PADS_D ;
названия будут: PCI_PADS_C, PCI_PADS_G, PCI_PADS_B и т.д.
Далее специальные названия для триггеров входных данных+комманд и выходных данных (команды я не использую)
... INST XPCI_CBIQ3 TNM = FFS:PCI_FFS_ICE ; INST XPCI_CBIQ2 TNM = FFS:PCI_FFS_ICE ; INST XPCI_CBIQ1 TNM = FFS:PCI_FFS_ICE ; INST XPCI_CBIQ0 TNM = FFS:PCI_FFS_ICE ; # INST XPCI_ADIQ31 TNM = FFS:PCI_FFS_ICE ; INST XPCI_ADIQ30 TNM = FFS:PCI_FFS_ICE ; INST XPCI_ADIQ29 TNM = FFS:PCI_FFS_ICE ; ... INST PCI/AD_O_0 TNM = FFS:PCI_FFS_OCE ; INST PCI/AD_O_1 TNM = FFS:PCI_FFS_OCE ; INST PCI/AD_O_2 TNM = FFS:PCI_FFS_OCE ; INST PCI/AD_O_3 TNM = FFS:PCI_FFS_OCE ; ... INST PCI/PING_DONE TNM = FFS:USER_FFS;
обратите в нимание, что при работе с шинами, в верилоге у нас выход с регистром называется AD_O, в процессе компиляции он разбивается на провода и соответственно в файле с константами его нужно указывать как AD_O_xxx
Если что-то удалилось из дизайна в процессе оптимиазации, то также эти регистры нельзя указать в файле с константами и это будет вызывать ошибки.
Следует отметить что поскольку некоторые сигналы PCI коммутируются одновременно (TRDY и STOP например), то компилятор будет считать их эквивалентными и пытаться убрать второй регистр, это крайне нежелательно, по причинам которые обьясню чуть позже, но пока лишь скажу что для того чтобы этого не происходило нужно убрать галку с
Equivalent Register Removal в настройках синтеза.
Далее определяем одни группы через логические операции над другими:
TIMEGRP "ALL_FFS" = FFS : EXCEPT : "USER_FFS" ; TIMEGRP "FAST_FFS" = "PCI_FFS_ICE" : "PCI_FFS_OCE" ; TIMEGRP "SLOW_FFS" = PCI_FFS : EXCEPT : "FAST_FFS" ;
Далее когда названия групп определены, переходим к спецификации констрейнтов оптимизации (скопировано из коры как есть):
################################################################################ # Time Specs ################################################################################ # # Important Note: The timespecs used in this section cover all possible # paths. Depending on the design options, some of the timespecs may # not contain any paths. Such timespecs are ignored by PAR and TRCE. # # Note: Timespecs are derived from the PCI Bus Specification, the # minimum clock delay of 0.000 ns, the maximum clock delay of 3.000 ns, # and a 90% tracking ratio between clock and data paths. # # Then, for paths on the primary global clock network: # # 1) Clk To Out = 11.000ns - 3.000ns = 8.000ns # 2) Setup = 7.000ns + 0.90 * 0.000ns = 7.000ns # 3) Grant Setup = 10.000ns + 0.90 * 0.000ns = 10.000ns # 4) AD/CBE Toff = 28.000ns - 3.000ns = 25.000ns # 5) AD/CBE Ton = 30.000ns + 11.000ns - 3.000ns = 38.000ns # 6) Period = = 30.000ns # # The following timespecs are for setup specifications. When using a # single clock, these timespecs are merged as pads-to-all. # TIMESPEC TS_ADF_SETUP = FROM : "PCI_PADS_D" : TO : ALL_FFS : 7.000 ; TIMESPEC TS_PAF_SETUP = FROM : "PCI_PADS_P" : TO : ALL_FFS : 7.000 ; TIMESPEC TS_BYF_SETUP = FROM : "PCI_PADS_B" : TO : ALL_FFS : 7.000 ; TIMESPEC TS_CNF_SETUP = FROM : "PCI_PADS_C" : TO : ALL_FFS : 7.000 ; TIMESPEC TS_GNF_SETUP = FROM : "PCI_PADS_G" : TO : ALL_FFS : 10.000 ; # # All critical input and output is registered to ensure clock to out # specifications are met by silicon. When using a single clock, these # timespecs are merged as all-to-pads. # TIMESPEC TS_CNF_CKOUT = FROM : ALL_FFS : TO : "PCI_PADS_C" : 8.000 ; TIMESPEC TS_GNF_CKOUT = FROM : ALL_FFS : TO : "PCI_PADS_G" : 8.000 ; # # Similar to above, the critical input and output paths are registered # to ensure clock to out specifications are made by silicon. Since this # interface uses address stepping, the clock to valid and clock to data # have different specifications. # TIMESPEC TS_ADF_CKOUT = FROM : "FAST_FFS" : TO : "PCI_PADS_D" : 8.000 ; TIMESPEC TS_ADS_TSOUT = FROM : "SLOW_FFS" : TO : "PCI_PADS_D" : 25.000 ; # TIMESPEC TS_BYF_CKOUT = FROM : "FAST_FFS" : TO : "PCI_PADS_B" : 8.000 ; TIMESPEC TS_BYS_TSOUT = FROM : "SLOW_FFS" : TO : "PCI_PADS_B" : 25.000 ; # TIMESPEC TS_PAF_CKOUT = FROM : "FAST_FFS" : TO : "PCI_PADS_P" : 8.000 ; TIMESPEC TS_PAS_TSOUT = FROM : "SLOW_FFS" : TO : "PCI_PADS_P" : 25.000 ; # # The design may be covered by a default period constraint. This is # generally sufficient when using a single clock. The period should # be set at the minimum PCI Bus clock period. # NET "PCLK" PERIOD = 30.000;
Если такой файл попробовать имплементировать, то имплементация выдаст ошибку таймингов.
Для того чтобы тайминги соответствовали спецификации PCI, нужно запихнуть входные триггеры, выходные триггеры а также триггеры Output Enable (OE_) в блоки ввода-вывода (IOB)
Для этого используются вот такие директивы:
INST "XPCI_CBIQ3" IOB = TRUE ; INST "XPCI_CBIQ2" IOB = TRUE ; INST "XPCI_CBIQ1" IOB = TRUE ; INST "XPCI_CBIQ0" IOB = TRUE ; # INST "XPCI_ADIQ31" IOB = TRUE ; INST "XPCI_ADIQ30" IOB = TRUE ; INST "XPCI_ADIQ29" IOB = TRUE ; INST "XPCI_ADIQ28" IOB = TRUE ; ... INST XPCI_IDSELIQ IOB = TRUE; INST XPCI_SERRIQ IOB = TRUE; INST XPCI_PERRIQ IOB = TRUE; INST XPCI_FRAMEIQ IOB = TRUE; INST XPCI_IRDYIQ IOB = TRUE; INST XPCI_DEVSELIQ IOB = TRUE; INST PCI/DEVSEL_O_N IOB = TRUE; INST PCI/OE_DEVSEL_N IOB = TRUE; INST XPCI_STOPIQ IOB = TRUE; INST XPCI_TRDYIQ IOB = TRUE; INST PCI/TRDY_O_N IOB = TRUE; INST PCI/OE_TRDY_N IOB = TRUE; INST PCI/STOP_O_N IOB = TRUE; INST PCI/OE_STOP_N IOB = TRUE; INST PCI/PAR_O IOB = TRUE; INST PCI/OE_PAR_N IOB = TRUE; INST PCI/AD_O_0 IOB = TRUE; INST PCI/AD_O_1 IOB = TRUE; INST PCI/AD_O_2 IOB = TRUE; INST PCI/AD_O_3 IOB = TRUE; ...
После этого все должно скомпилироваться успешно, а в конце маппинга, выдается вот такая вот таблица:
... | AD<28> | IOB | BIDIR | PCI33_5 | | | INFF | | IFD | | | | | | | | OUTFF | | | | AD<29> | IOB | BIDIR | PCI33_5 | | | INFF | | IFD | | | | | | | | OUTFF | | | | AD<30> | IOB | BIDIR | PCI33_5 | | | INFF | | IFD | | | | | | | | OUTFF | | | | AD<31> | IOB | BIDIR | PCI33_5 | | | INFF | | IFD | | | | | | | | OUTFF | | | | CBE_N<0> | IOB | BIDIR | PCI33_5 | | | INFF | | IFD | | CBE_N<1> | IOB | BIDIR | PCI33_5 | | | INFF | | IFD | | CBE_N<2> | IOB | BIDIR | PCI33_5 | | | INFF | | IFD | | CBE_N<3> | IOB | BIDIR | PCI33_5 | | | INFF | | IFD | | DEVSEL_N | IOB | BIDIR | PCI33_5 | | | OUTFF | | | | | | | | | | ENFF | | | | FRAME_N | IOB | BIDIR | PCI33_5 | | | INFF | | IFD | | IDSEL | IOB | INPUT | PCI33_5 | | | INFF | | IFD | | IRDY_N | IOB | BIDIR | PCI33_5 | | | INFF | | IFD | | PAR | IOB | BIDIR | PCI33_5 | | | OUTFF | | | | | | | | | | ENFF | | | | PING_DONE | IOB | OUTPUT | LVTTL | 12 | SLOW | OUTFF | | | | RST_N | IOB | INPUT | PCI33_5 | | | | | | | STOP_N | IOB | BIDIR | PCI33_5 | | | OUTFF | | | | | | | | | | ENFF | | | | TRDY_N | IOB | BIDIR | PCI33_5 | | | OUTFF | | | | | | | | | | ENFF | | | +------------------------------------------------------------------------------------------------------------------------+
Обратите внимание на INFF, OUTFF и ENFF.
Я использовал ровно такие настройки как и старая кора.
Далее собственно имплементация интерфейса.
я разделил это на три блока:
сигналы PCI и управление шиной:
reg IsWrite;
wire BAR1Matches = (CBE_I_N[3:1] == 3'b001) & (AD_I[31:BAR1_WINDOW_BITS]==Bar1Addr[31:BAR1_WINDOW_BITS]) & (AD_I[1:0] == 2'b00);
wire BAR2Matches = (CBE_I_N[3:1] == 3'b011) & (AD_I[31:BAR2_WINDOW_BITS]==Bar2Addr[31:BAR2_WINDOW_BITS]);
wire CFGMatches = (CBE_I_N[3:1] == 3'b101) & (AD_I[1:0] == 2'b00) & (AD_I[10:8] == 3'b000);
reg [1:0] AccessType; // local
parameter ACCESS_IO = 2'b00;
parameter ACCESS_MEM = 2'b01;
parameter ACCESS_CONFIG = 2'b10;
always @(posedge CLK)
begin
case(Transaction)
TX_STOP: begin
Transaction <= TX_IDLE;
end
TX_DEVSEL: begin
Transaction <= TX_TRDY;
end
TX_TRDY: begin
if (FRAME_I_N)
Transaction <= TX_STOP;
end
default: // TX_IDLE
begin
if (~FRAME_I_N & ((IDSEL_I & CFGMatches) | (PCICommandIOSpaceBit0 & BAR1Matches) | (PCICommandMEMSpaceBit1 & BAR2Matches)))
Transaction <= TX_DEVSEL;
end
endcase
end
always @(posedge CLK)
begin
if (Transaction == TX_IDLE) begin
IsWrite <= CBE_I_N[0];
AccessType <= {CBE_I_N[3], CBE_I_N[2]};
CurrentAddr <= (IDSEL_I ? AD_I[7:2] :
(CBE_I_N[2] ? {32'b0, AD_I[BAR2_WINDOW_BITS-1:2]} :
{32'b0, AD_I[BAR1_WINDOW_BITS-1:2]}));
end
DEVSEL_O_N <= ~(Transaction == TX_DEVSEL | Transaction == TX_TRDY);
OE_DEVSEL_N <= ~(Transaction == TX_DEVSEL | Transaction == TX_TRDY | Transaction == TX_STOP);
TRDY_O_N <= ~(Transaction == TX_TRDY);
OE_TRDY_N <= ~(Transaction == TX_TRDY | Transaction == TX_STOP);
STOP_O_N <= ~(Transaction == TX_TRDY);
OE_STOP_N <= ~(Transaction == TX_TRDY | Transaction == TX_STOP);
OE_AD_N <= ~((Transaction == TX_DEVSEL | Transaction == TX_TRDY) & ~IsWrite);
OE_PAR_N <= ~((Transaction == TX_DEVSEL | Transaction == TX_TRDY | Transaction == TX_STOP) & ~IsWrite);
// Output parity with 1 clock shift
PAR_O <= ^{AD_I, CBE_I_N};
end
Ввод данных:
always @(posedge CLK)
begin
if (Transaction == TX_TRDY & AccessType == ACCESS_CONFIG & IsWrite)
case (CurrentAddr)
1: begin
if (~CBE_I_N[0]) begin
PCICommandIOSpaceBit0 <= AD_I[0];
PCICommandMEMSpaceBit1 <= AD_I[1];
end
if (~CBE_I_N[1])
PCICommandIntrDisableBit10 <= AD_I[10];
end
4: begin
Bar1Addr[31:BAR1_WINDOW_BITS] <= AD_I[31:BAR1_WINDOW_BITS];
end
5: begin
Bar2Addr[31:BAR2_WINDOW_BITS] <= AD_I[31:BAR2_WINDOW_BITS];
end
15: begin
if (~CBE_I_N[0])
PCIInterruptLine <= AD_I[7:0];
end
endcase
end
always @(posedge CLK)
begin
if (Transaction == TX_TRDY & IsWrite & AccessType == ACCESS_MEM)
mem[CurrentAddr] <= AD_I;
end
always @(posedge CLK)
begin
if (Transaction == TX_TRDY & IsWrite & AccessType == ACCESS_IO)
io[CurrentAddr] <= AD_I;
end
Вывод данных:
always @(posedge CLK)
begin
case (AccessType)
ACCESS_CONFIG: begin
case(CurrentAddr)
0: AD_O <= {CFG_DEVICE, CFG_VENDOR};
1: AD_O <= {PCIStatus, PCICommand};
2: AD_O <= {CFG_CC, CFG_REVISION};
4: AD_O <= {Bar1Addr[31:BAR1_WINDOW_BITS], {(BAR1_WINDOW_BITS-1){1'b0}}, /* IO space */1'b1};
5: AD_O <= {Bar2Addr[31:BAR2_WINDOW_BITS], {(BAR2_WINDOW_BITS-4){1'b0}}, /* MEM space, Prefetch=true,location=32bit */4'b1000};
15: AD_O <= {PCIMaxLat, PCIMinGnt, PCIInterruptPin, PCIInterruptLine};
16: AD_O <= {32'b0, Bar1Addr};
17: AD_O <= {32'b0, Bar2Addr};
default: AD_O <= 32'b0;
endcase
end
ACCESS_MEM : begin
AD_O <= mem[CurrentAddr];
end
ACCESS_IO : begin
AD_O <= io[CurrentAddr];
end
endcase
end
как видно я использую два бара, один бар ввода вывода, а другой памяти.
размеры задаются параметрами:
parameter BAR1_WINDOW_BITS = 4; parameter BAR2_WINDOW_BITS = 4;
следует иметь ввиду, что размер окна памяти (BAR2_) не может быть меньше 16 адресов (потому как первые 4 бита конфигурационного регистра зарезервивованы) а размер окна I/O не может быть меньше 4 адресов , потому как адресация I/O 8-битная, и только два младших бита зарезервивованы.
Я жестко требую выравнивания памяти при обращении. Byte Enable не использую все должно быть выравнено на 32 бита.
Далее важно проверить соответствие спецификации, конечной Post-Route симуляции!
Например я пытался слишком быстро устанавливать TRDY в результате чего адреса читались как нули (не успевали устаовиться) я это исправил задержав TRDY на один клок, это кстати быстрее оригинальной коры на один клок, у них задерживалось на два клока относительно DEVSEL.
Генерируем файл верилога для конечной симуляции:

это создаст файлы в папке nergen.
Создадим новый .do файл для Modelsim: timesim.do
vlib work vlog -f cnc_tb_timesim.f vsim -novopt cnc_tb glbl view signals structure wave add wave -logic /CLK add wave -logic /RST_N add wave -literal -hex /AD add wave -literal -hex /CBE add wave -logic /PAR add wave -logic /FRAME_N add wave -logic /IRDY_N add wave -logic /TRDY_N add wave -logic /STOP_N add wave -logic /DEVSEL_N add wave -logic /REQ_N add wave -logic /GNT_N add wave -logic /SERR_N add wave -logic /PERR_N add wave -logic /IDSEL add wave -logic /INTR_A add wave -label "OPERATION" -radix ascii /cnc_tb/STM/operation run -all
Создадим новый .f файл для Modelsim: cnc_tb_timesim.f
+licq_all+ +access+r ./netgen/par/cnc_top_timesim.v ./busrecord.v ./dumb_arbiter.v ./dumb_targ32.v ./stimulus.v ./cnc_tb.v +libext+.vmd+.v -y $XILINX/verilog/src/unisims -y $XILINX/verilog/src/simprims
Запустим симуляцию:
проверим чтобы нигде не было красного (1'bx) что означает неинициализированные данные.
а синее (1'bz) какраз было в так называемых turnaround циклах PCI. естественно нас интересуют:
AD, CBE, TRDY, DEVSEL, STOP, PAR.
да, важно понять что PAR устанавливатся на следующем клоке, т.е. сначала мы пишем данные, а на следующем клоке устанавливаем их parity. и так это сдвинуто на протяжении всей транзакции (если использовать Burst, что мне не нужно):

Далее записываем новую прошивку в устройство, выключаем питание, загружаемся, драйвер показывает новые ресурсы.
проверяем соответствие верилогу:

no subject
Date: 2014-06-08 06:40 pm (UTC)в моем случае их нет, и использование = генерирует меньше элементов, потому как <= стабильно генерирует триггеры, а = иногда оптимизируется.
2) где можно упростить? я кидаю в 3 always а не в один.
no subject
Date: 2014-06-08 07:00 pm (UTC)2. По-хорошему каждый триггер пишется своим always. Ну или если у вас пачка триггеров, работающих на одинаковую задачу, то их можно сунуть в один always-begin-end.
Далее, пример Си-стайла:
IsMemory = 1'b1;
Ну как-то в цифровой схеме присваивать константу не по сбросу... Всё равно что кидать вход на питание или землю.
Переписывается в такое вот примерно:
always @(posedge CLK)
IsMemory <= IsMemory ? ~(PCICommandIOSpaceBit0 & BAR1Matches) : PCICommandMEMSpaceBit0 & BAR2Matches;
Просто опять же, синтезаторы у САПР ныне мощные. У Quartus получше, у Vivado похуже. Но и у них бывает (и ещё как бывает) сносит крышу, и кроме удобства читаемости вы можете нарваться на то, что он сделает работающую, но неоптимальную логику. При невысоких для нынешних ПЛИС PCI-ных частотах это пока что неважно. Но потом вы можете что-то не вытянуть по времянке.
no subject
Date: 2014-06-08 07:09 pm (UTC)2) это как-то генерирует меньше LUT? я попробую переписать. Да, группа выходов PCI должнда описываться в одном Always, мogu вынести промежуточные сигналы в свои always.
IsMemory <= IsMemory ? ~(PCICommandIOSpaceBit0 & BAR1Matches) : PCICommandMEMSpaceBit0 & BAR2Matches;
боже. что это? почему у вас зависит от предыдущего значения IsMemory?
PCICommandIOSpaceBit0 & BAR1Matches валидно только на первом такте PCI оно должно защекливаться на всех других до конца транзакции. оператором набла это не описать.
no subject
Date: 2014-06-08 07:15 pm (UTC)2. Потому что у вас по какому-то условию регистру присваивается 1. Так? А по второму триггер сбрасывается в ноль. Потому и стоит оператор "?": если IsMemory = "0", то по "PCICommandMEMSpaceBit0 & BAR2Matches" триггер становится равным "1". А скидывается уже по "PCICommandIOSpaceBit0 & BAR1Matches".
LUT может меньше и не генерировать, но вы сами ж через некоторое время запутаетесь в своём коде. Честное слово. :)
Контроллеры вообще хорошо писать на стейт-машине, по которой потом переключатся триггеры.
no subject
Date: 2014-06-08 07:27 pm (UTC)2) логика такая:
а) Может быть: PCICommandIOSpaceBit0 & BAR1Matches, тогда нужно 0
б) Может быть: PCICommandMEMSpaceBit0 & BAR2Matches
в) Может не быть ни того не другого
все это проверяется на первом такте в фазе адресации. IsMemory это "защелка" которая нужна на следущих тактах
тогда уже:
IsMemory = PCICommandMEMSpaceBit0 & BAR2Matches
этого достаточно, потому что в других случаях IsConfig сработает а IsIo не нужно, по остаточному принципу
логика совпадает полностью с логикой присваивания CurrentAddr которая тоже зависит от техже условий, поэтому обьеденено в один always.
не знаю нужно ли выносить в отдельные always если это не принесет выигрыш по LUT
no subject
Date: 2014-06-08 07:34 pm (UTC)_____
Я не спорю, что логика сейчас работает. Это видно хотя бы по тому, что устройство нашлось.
Но вот вы показали здесь код. Ну, написан он неряшливо у вас. Если кто-то не знал верилога и решил, что-то почитав, понять, что вы написали, то он ничего не поймёт.
no subject
Date: 2014-06-08 07:35 pm (UTC)no subject
Date: 2014-06-08 07:39 pm (UTC)no subject
Date: 2014-06-08 07:53 pm (UTC)если вы его запишете под if TransactionStart оно работать будет, но ровно также будет работать и простое присваивание которое я написал.
no subject
Date: 2014-06-08 08:00 pm (UTC)Ну не суть. Захотелось переписать потому, что константы в присвоении глаз режут.
no subject
Date: 2014-06-08 08:14 pm (UTC)походу если присваиваем константы оптимизатор хуже работает, потому как не знает корреляцию между константами на разных присваиваниях.
no subject
Date: 2014-06-09 12:23 am (UTC)reg IsWrite; wire DataRead = (Transaction == TX_DEVSEL | Transaction == TX_TRDY) & ~IsWrite; wire DataWrite = (Transaction == TX_TRDY) & ~IRDY_I_N & IsWrite; wire BAR1Matches = (CBE_I_N[3:1] == 3'b001) & (AD_I[31:BAR1_WINDOW_BITS]==Bar1Addr[31:BAR1_WINDOW_BITS]) & (AD_I[1:0] == 2'b00); wire BAR2Matches = (CBE_I_N[3:1] == 3'b011) & (AD_I[31:BAR2_WINDOW_BITS]==Bar2Addr[31:BAR2_WINDOW_BITS]); wire CFGMatches = (CBE_I_N[3:1] == 3'b101) & (AD_I[1:0] == 2'b00) & (AD_I[10:8] == 3'b000); reg [1:0] AccessType; // local parameter ACCESS_IO = 2'b00; parameter ACCESS_MEM = 2'b01; parameter ACCESS_CONFIG = 2'b10; always @(posedge CLK) begin case(Transaction) TX_STOP: begin Transaction <= TX_IDLE; end TX_DEVSEL: begin Transaction <= TX_TRDY; end TX_TRDY: begin if (FRAME_I_N) Transaction <= TX_STOP; end default: // TX_IDLE begin if (~FRAME_I_N & ((IDSEL_I & CFGMatches) | (PCICommandIOSpaceBit0 & BAR1Matches) | (PCICommandMEMSpaceBit1 & BAR2Matches))) Transaction <= TX_DEVSEL; end endcase end always @(posedge CLK) begin CurrentAddr <= (Transaction == TX_IDLE) ? (IDSEL_I ? AD_I[7:2] : (CBE_I_N[2] ? {32'b0, AD_I[BAR2_WINDOW_BITS-1:2]} : {32'b0, AD_I[BAR1_WINDOW_BITS-1:2]})) : CurrentAddr; IsWrite <= (Transaction == TX_IDLE) ? CBE_I_N[0] : IsWrite; AccessType <= (Transaction == TX_IDLE) ? {CBE_I_N[3], CBE_I_N[2]} : AccessType; DEVSEL_O_N <= ~(Transaction == TX_DEVSEL | Transaction == TX_TRDY); OE_DEVSEL_N <= ~(Transaction == TX_DEVSEL | Transaction == TX_TRDY | Transaction == TX_STOP); TRDY_O_N <= ~(Transaction == TX_TRDY); OE_TRDY_N <= ~(Transaction == TX_TRDY | Transaction == TX_STOP); STOP_O_N <= ~(Transaction == TX_TRDY); OE_STOP_N <= ~(Transaction == TX_TRDY | Transaction == TX_STOP); OE_AD_N <= ~((Transaction == TX_DEVSEL | Transaction == TX_TRDY) & ~IsWrite); OE_PAR_N <= ~((Transaction == TX_DEVSEL | Transaction == TX_TRDY | Transaction == TX_STOP) & ~IsWrite); // Output parity with 1 clock shift // Parity calculation PAR_O <= LastParity; AD_O <= CurrentOutput; end // Transfer handling always @(posedge CLK) begin if (DataRead) // Read transaction? case (AccessType) ACCESS_CONFIG: begin case(CurrentAddr) 0: CurrentOutput <= {CFG_DEVICE, CFG_VENDOR}; 1: CurrentOutput <= {PCIStatus, PCICommand}; 2: CurrentOutput <= {CFG_CC, CFG_REVISION}; 4: CurrentOutput <= {Bar1Addr[31:BAR1_WINDOW_BITS], {(BAR1_WINDOW_BITS-1){1'b0}}, /* IO space */1'b1}; 5: CurrentOutput <= {Bar2Addr[31:BAR2_WINDOW_BITS], {(BAR2_WINDOW_BITS-4){1'b0}}, /* MEM space, Prefetch=true,location=32bit */4'b1000}; 15: CurrentOutput <= {PCIMaxLat, PCIMinGnt, PCIInterruptPin, PCIInterruptLine}; 16: CurrentOutput <= {32'b0, Bar1Addr}; 17: CurrentOutput <= {32'b0, Bar2Addr}; default: CurrentOutput <= 32'b0; endcase end ACCESS_MEM : begin CurrentOutput <= mem[CurrentAddr]; end ACCESS_IO : begin CurrentOutput <= io[CurrentAddr]; end endcase LastParity <= ^{CurrentOutput, CBE_I_N}; end ...пока вот так переписал
no subject
Date: 2014-06-09 12:28 am (UTC)always @(posedge CLK) begin if (DataWrite) // Write transaction? case (AccessType) ACCESS_CONFIG: begin case(CurrentAddr) 1: begin if (~CBE_I_N[1]) PCICommandIntrDisableBit10 = AD_I[10]; if (~CBE_I_N[0]) begin PCICommandIOSpaceBit0 = AD_I[0]; PCICommandMEMSpaceBit1 = AD_I[1]; end end 4: begin Bar1Addr[31:BAR1_WINDOW_BITS] = AD_I[31:BAR1_WINDOW_BITS]; end 5: begin Bar2Addr[31:BAR2_WINDOW_BITS] = AD_I[31:BAR2_WINDOW_BITS]; end 15: begin if (~CBE_I_N[0]) PCIInterruptLine = AD_I[7:0]; end default: begin end // do nothing endcase end ACCESS_MEM : begin mem[CurrentAddr] = AD_I; end ACCESS_IO : begin io[CurrentAddr] = AD_I; end endcase endа тут тоже все переписывать оператором набла?
PCICommandIOSpaceBit0 = (AccessType == ACCESS_CONFIG & DataWrite & (CurrentAddr == 1) & ~CBE_I_N[0]) ? AD_I[0] : PCICommandIOSpaceBit0;
(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2014-06-08 08:12 pm (UTC)IsMemory = PCICommandMEMSpaceBit1 & BAR2Matches;
генерируется меньше LUT
260 => 248
я попробую переписать все остальное с целью убирания присваивания констант
no subject
Date: 2014-06-08 07:16 pm (UTC)no subject
Date: 2014-06-08 07:50 pm (UTC)no subject
Date: 2014-06-08 07:54 pm (UTC)no subject
Date: 2014-06-08 08:03 pm (UTC)как можно получить неверные результаты при использовании блокируещего присваивания в статье ничего не говорится.
может я что-то пропустил конечно.
no subject
Date: 2014-06-08 08:42 pm (UTC)always @(posedge clk) begin
x = x + 1;
y = x;
end
Если у вас x=3, то на следующий такт в железке будет x = 4, y = 3, а вот при моделировании x=4, y = 4.
no subject
Date: 2014-06-08 09:00 pm (UTC)но как мне поступить например с CurrentOutput который еще и используется для подсчета parity.
тоже переписывать неблокирующими присваиваниями? но тогда мне нужно будет скопировать целый case блок для переиспользования техже данных что и для AD_O, я так пробовал, и генерируется куча LUT.
no subject
Date: 2014-06-08 09:08 pm (UTC)Или я не понял проблемы?
(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2014-06-08 09:05 pm (UTC)есть только такой вывод:
"Эти две записи эквивалентны. Но вторую запись нельзя понимать как последовательную цепочку вычислений. Это верно лишь в том смысле, что всё выражение действительно выстраивается в схему, в которой сначала отрезаются 4 младших разряда, результат и второй операнд идут на вход сумматора, а выход сумматора отдается умножителю на три. Так это представляется в электрической схеме и человек для удобства нарисует эту схему слева направо и читать он ее будет последовательно. Но в получившейся схеме все это выражение выполняется непрерывно, так же как и в предыдущей записи с неблокирующим присваиванием. Запись результата в регистр, как и следовало ожидать, происходит по фронту тактового импульса."
no subject
Date: 2014-06-08 09:15 pm (UTC)"Блокирующее присваивание, заклеймленное некорыми как «медленное», в действительности во многих случаях синтезируется в совершенно ту же схему, что и неблокирующее."
"Чтобы записать это выражение неблокирующими присваиваниями, потребовалась бы такая запись:"
То есть разница есть. Но синтез-то произойдёт именно так, как будто у вас неблокирующее присваивание, несмотря на запись без "<".
no subject
Date: 2014-06-08 09:10 pm (UTC)это так будет только при условии что х и y в процессе синтеза будут синтезированы как регистры.
если же сигнал x будет wire и только y будет в регистре, тогда схема будет работать везде x=4, y = 4.
по причине "непрерывности" которая в статье описана.
я кстати не уверен в справедливости вашего утверждения пока сам не увижу такой разницы.
no subject
Date: 2014-06-08 09:16 pm (UTC)(no subject)
From:(no subject)
From: