Оригинал взят у
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 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
Date: 2014-06-08 09:32 pm (UTC)поэтому удобно парити считать сразу.
для того чтобы это сделать вам нужно "CurrentOutput" который используется как локальная переменная.
если вы захотите переписать его как неблокориющее присваивание, тогда нужно будет скопировать большой Case два раза. один раз для вывода данных, второй раз для вывода parity.
(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: