Оригинал взят у
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 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
Date: 2014-06-08 09:38 pm (UTC)Кстати, и по "AD_O вы использовать не можете" у меня вопросы, с чего я не могу его использовать, я ж с IOBUF могу взять входные данные?
no subject
Date: 2014-06-08 11:54 pm (UTC)с самого IOBUF не пробовал(имеется ввиду например с AD_IN15 или AD[15] ?), но в триггере сразу за ним (который помещается в INFF) в этот момент находятся иксы (по крайней мере на симуляции)
попробую с IOBUF взять, тогда CurrentOutput не нужен.
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
Date: 2014-06-08 09:21 pm (UTC)reg в верилоге не обязательно синтезируется в регистр, он может и быть просто соединен проводом если используется как "локальная переменная"
я не уверен что по спецификации верилога если у вас есть блокирующие присваивания в одном always вы можете получить в железе x = 4, y = 3
no subject
Date: 2014-06-08 09:24 pm (UTC)Ничего другого в железе вы не получите. Это D-trigger (ну, так ячейка ПЛИСины устроена), он работает по фронту и ни о каком присваивании или спецификации не знает. У него есть задача: хватать и тащить на выход то, что в момент фронта на входе. А на входе у него в одном случае 3, в другом = 3+1. Всё.