Here's an interesting problem. You want to simulate code from Lattice along with code from Xilinx. (For example when you are simulating a Xilinx PCIe Root Complex against a Lattice PCIe Endpoint!)
Of course you couldn't attempt this without partitioning into libraries (because of name conflicts). So perhaps you have a library called lattice_work and xilinx_work. When compiling the code there are primitives like NAND, NOR, XOR... that are used in the both sets of code. The Lattice code will need to use Lattice's primitives, and the Xilinx code will need to use Xilinx's primitives.
You can use the +v and -y options to have the primitives found and compiled into the lattice_work and xilinx_work libraries, and then you can use the -L work trick (see previous blog http://ionipti.blogspot.com/2010/07/simulating-2-lattice-pcie-cores.html).
The problem with this method is that now whenever you run vlog with the incr flag, it will not only check if the Lattice and Xilinx code is up to date, it will also go and check the primitives! That's a huge waste of time! Also, your lattice_work and xilinx_work libraries will get filled with lots of primitives that you don't care to see.
What I like to do is to compile all the primitives into separate libraries. For example I have for Lattice, ecp3_work and pmi_work. For Xilinx I have unisims_work and secureip_work. The primitives are in ecp3_work and unisims_work. This is a nice method, and of course saves time since I never have to recompile or check for updates on these files. The drawback is that the Lattice code and Xilinx code will arbitrarily pick out the NAND, NOR, XOR, etc primitives and will of course cause lots of errors.
I prefer to not have to add source code modifiers, ie I'd like the source code to not have to change in order to fix this. `uselib is a great solution that typically requires it to be placed before the source. My solution has been to create uselib_xilinx.v and uselib_lattice.v files and add that file in the vlog command per each source file.
For example, instead of calling
vlog lattice_code.v
I now call
vlog uselib_lattice.v lattice_code.v
uselib_lattice.v looks like this:
`uselib dir=ecp3_work dir=pmi_work
This works because the `uselib attribute persists through the vlog command. So even if you call vlog with many files, the `uselib will persist. This of course would be problematic if you have additional `uselib calls in your code since they will override the uselib_lattice's or uselib_xilinx's `uselib attribute.
I wish Modelsim would fix their library issues. The solution of -L work is very problematic, and leads to the bug I describe here (http://ionipti.blogspot.com/2010/07/modelsim-working-with-multiple.html). If they would only add some nice switches into the vlog and vsim commands it would make life much easier.
Wednesday, August 11, 2010
SystemVerilog disable fork
The short of it is that 'disable fork' is a pain. The long of it is that you have no choice, and perhaps an example might make your lives easier:
fork
begin
fork
begin
#3000;
$display("timeout");
end
begin
sem.get(1);
end
join_any
disable fork;
end
join
Why wrap it in another fork-join pair? This is due to how disable fork works. disable fork disables ALL forks at its level and below. If the above code is inside of a task which is part of a module, then calling disable fork without it being wrapped will disable all forks running in the module. I KNOW! It's ludicrous! Yes, but this is how it works.
What about named forks? Also problematic. This is because when you use a named fork and then call disable, it will disable all instances of the same named fork. If you have multiple threads running the same task and both are in the named fork, then one disable will kill both of them. I KNOW! It's ludicrous! ...
Any other options? I don't know. This seems to be the only way to do the job in SystemVerilog. Perhaps avoiding semaphores and doing everything with wait statements or events, but for now I prefer to use semaphores as they are an item I can 'new', and therefore they allow for much more flexibilty.
fork
begin
fork
begin
#3000;
$display("timeout");
end
begin
sem.get(1);
end
join_any
disable fork;
end
join
Why wrap it in another fork-join pair? This is due to how disable fork works. disable fork disables ALL forks at its level and below. If the above code is inside of a task which is part of a module, then calling disable fork without it being wrapped will disable all forks running in the module. I KNOW! It's ludicrous! Yes, but this is how it works.
What about named forks? Also problematic. This is because when you use a named fork and then call disable
Any other options? I don't know. This seems to be the only way to do the job in SystemVerilog. Perhaps avoiding semaphores and doing everything with wait statements or events, but for now I prefer to use semaphores as they are an item I can 'new', and therefore they allow for much more flexibilty.
Saturday, July 31, 2010
DDR3 Fly-by-topology and Write-leveling
The key is that the DQ/DQS signals are directly connected to each rank (or set of chips). So where the controls (clock, command, address, etc...) are connected in fly-by topology, the data and strobes are connected directly. This is why write-leveling is important. It indicates the skew between when the clock (and other control signals) arrive(s) and when the data (and strobes) arrive(s).
Also this is a nice explanation of rank and other stuff:
http://www.edaboard.com/ftopic342841.html
Also this is a nice explanation of rank and other stuff:
http://www.edaboard.com/ftopic342841.html
Thursday, July 29, 2010
SystemVerilog - Interfaces vs Classes
So I've been designing Verilog for a while and I recently started playing with SystemVerilog. As a programmer with C++ experience, I'm very familiar with OOP (Object Oriented Programming). But this didn't give me the complete understanding of SystemVerilog's paradigm. Here's the key differences in ideas between Interfaces and Classes.
Interfaces are like pointers to the actual signals. Interfaces live in the same realm as modules. Just like modules must be instantiated globally and not within a procedural block, so to interfaces must be instantiated globally. If you have a UART module (which contains the low level UART RX (receiver) and TX (transmitter)), and you have 3 different sets of UART wires (for 3 different UART ports). You may also have a UARTTest class which takes a UART and runs some tests on it. How do you build this?
Keep in mind that you don't want to always have to pass signals along. So in theory I want to create a UARTTest instance and have that instance store inside of it the signals required to control an individual UART.
You can't pass module instances around... This is where Interfaces come in. Here's an attempt without interfaces:
reg uart_tx [2:0]; //3 separate uart tx lines for 3 separate uarts
reg uart_rx [2:0]; //3 separate uart rx lines for 3 separate uarts
//not bothering to declare the rest of the signals... or anything else
UART uart_inst(.tx(uart_tx[0]), .rx(uart_rx[0]), ...);
UART uart_inst(.tx(uart_tx[1]), .rx(uart_rx[1]), ...);
UART uart_inst(.tx(uart_tx[2]), .rx(uart_rx[2]), ...);
initial begin
UARTTest t0;
UARTTest t1;
UARTTest t2;
t0 = new;
t0.runTest(uart_tx[0], uart_rx[0], ...);
t1 = new;
t1.runTest(uart_tx[1], uart_rx[1], ...);
t2 = new;
t2.runTest(uart_tx[2], uart_rx[2], ...);
end
The reason I have to send each signal above is because I have no way to store the signals in the class. Of course the main problems here are the verbosity of such calls. (I'm pretty sure what I've written above will work (with ref of course)... but even that's kind of unclear... I'll have to test this later.) Anyhow, the nicer and more proper way to do this is:
Create an interface with an rx and tx signal, and then instantiate them.
//signals are not declared at the top level, they're stuck in the interface.
uart_interface uart_interf_inst0 [2:0];
UART uart_inst(.tx(uart_interf_inst[0].tx), .rx(uart_interf_inst[0].rx), ...);
UART uart_inst(.tx(uart_interf_inst[1].tx), .rx(uart_interf_inst[1].rx), ...);
UART uart_inst(.tx(uart_interf_inst[2].tx), .rx(uart_interf_inst[2].rx), ...);
initial begin
UARTTest t0;
UARTTest t1;
UARTTest t2;
t0 = new(uart_interf_inst[0]);
t0.runTest();
t1 = new(uart_interf_inst[1]);
t1.runTest();
t2 = new(uart_interf_inst[2]);
t2.runTest();
end
Now the interface is stored in the class, and can be used. Since it's an interface, it actually controls real signals to the UART module. Remember to use as the parameter to new the virtual keyword as in:
function new(
virtual uart_interface interf
);
virtual for an interface implies something like a pointer to an interface. So it ends up being a pointer to the structure that contains all the pointers to the signals. Exactly what you need.
Interfaces are like pointers to the actual signals. Interfaces live in the same realm as modules. Just like modules must be instantiated globally and not within a procedural block, so to interfaces must be instantiated globally. If you have a UART module (which contains the low level UART RX (receiver) and TX (transmitter)), and you have 3 different sets of UART wires (for 3 different UART ports). You may also have a UARTTest class which takes a UART and runs some tests on it. How do you build this?
Keep in mind that you don't want to always have to pass signals along. So in theory I want to create a UARTTest instance and have that instance store inside of it the signals required to control an individual UART.
You can't pass module instances around... This is where Interfaces come in. Here's an attempt without interfaces:
reg uart_tx [2:0]; //3 separate uart tx lines for 3 separate uarts
reg uart_rx [2:0]; //3 separate uart rx lines for 3 separate uarts
//not bothering to declare the rest of the signals... or anything else
UART uart_inst(.tx(uart_tx[0]), .rx(uart_rx[0]), ...);
UART uart_inst(.tx(uart_tx[1]), .rx(uart_rx[1]), ...);
UART uart_inst(.tx(uart_tx[2]), .rx(uart_rx[2]), ...);
initial begin
UARTTest t0;
UARTTest t1;
UARTTest t2;
t0 = new;
t0.runTest(uart_tx[0], uart_rx[0], ...);
t1 = new;
t1.runTest(uart_tx[1], uart_rx[1], ...);
t2 = new;
t2.runTest(uart_tx[2], uart_rx[2], ...);
end
The reason I have to send each signal above is because I have no way to store the signals in the class. Of course the main problems here are the verbosity of such calls. (I'm pretty sure what I've written above will work (with ref of course)... but even that's kind of unclear... I'll have to test this later.) Anyhow, the nicer and more proper way to do this is:
Create an interface with an rx and tx signal, and then instantiate them.
//signals are not declared at the top level, they're stuck in the interface.
uart_interface uart_interf_inst0 [2:0];
UART uart_inst(.tx(uart_interf_inst[0].tx), .rx(uart_interf_inst[0].rx), ...);
UART uart_inst(.tx(uart_interf_inst[1].tx), .rx(uart_interf_inst[1].rx), ...);
UART uart_inst(.tx(uart_interf_inst[2].tx), .rx(uart_interf_inst[2].rx), ...);
initial begin
UARTTest t0;
UARTTest t1;
UARTTest t2;
t0 = new(uart_interf_inst[0]);
t0.runTest();
t1 = new(uart_interf_inst[1]);
t1.runTest();
t2 = new(uart_interf_inst[2]);
t2.runTest();
end
Now the interface is stored in the class, and can be used. Since it's an interface, it actually controls real signals to the UART module. Remember to use as the parameter to new the virtual keyword as in:
function new(
virtual uart_interface interf
);
virtual for an interface implies something like a pointer to an interface. So it ends up being a pointer to the structure that contains all the pointers to the signals. Exactly what you need.
Monday, July 26, 2010
Help!!! - DDR3 ODT (On Die Termination)???
Gosh, figuring this stuff out is difficult. Anyone know how this actually works? Why is Rtt Nom variable? When is it in use? If Dynamic ODT is on then Rtt Nom is really only in use when nothing is going across the wires. RTT Wr is used during a write, and Rtt is disabled during a read. So what is Rtt Nom really used for? It can't be for when Dynamic ODT is off, because that still wouldn't explain why there are 2 settings. Is it needed to keep the wires clean and not bouncing when nothing is going across it? Perhaps, but then why are there 6 possible values? What am I missing????
I can't continue until I completely understand this stuff. Perhaps I need to take some analog classes, or read some analog for dummies books, and then this would become clear... Although I doubt it.
Saddened by my lack of understanding here.
ANSWER!!!
It makes me so happy to learn something new, and that happened in spades tonight! Well at least I learned a bunch of analog stuff, and got a great explanation for Rtt Nom.
The key is that when there are multiple DDR3 chips hooked up, and you are only writing to one of them, then Rtt Wr could be used for the one you are writing to, but the other one which shares the same DQ/DQS/DM lines needs to be terminated with Rtt Nom which must be calibrated because it will affect the signal integrity of the data being written. This is the explanation. I can't claim complete understanding or comprehension since I've not got near enough analog knowledge or experience, but for my simplistic understanding, this suffices very well!
So much happier now :)
I can't continue until I completely understand this stuff. Perhaps I need to take some analog classes, or read some analog for dummies books, and then this would become clear... Although I doubt it.
Saddened by my lack of understanding here.
ANSWER!!!
It makes me so happy to learn something new, and that happened in spades tonight! Well at least I learned a bunch of analog stuff, and got a great explanation for Rtt Nom.
The key is that when there are multiple DDR3 chips hooked up, and you are only writing to one of them, then Rtt Wr could be used for the one you are writing to, but the other one which shares the same DQ/DQS/DM lines needs to be terminated with Rtt Nom which must be calibrated because it will affect the signal integrity of the data being written. This is the explanation. I can't claim complete understanding or comprehension since I've not got near enough analog knowledge or experience, but for my simplistic understanding, this suffices very well!
So much happier now :)
Friday, July 23, 2010
modelsim scripting - tcl
Spent some time scripting with Modelsim, and here are the lessons:
quietly / transcript off | on | quiet ...
Modelsim by default echoes all set commands to the console. This is b/c of Modelsim's transcript setting. You can either call 'quietly set ...' or just set 'transcript off' at the top. echo will stop working, but you shouldn't use that anyhow, use 'puts ...' in place of 'echo ...' for compatibility across operating systems.
onerror {resume} ... doesn't actually do everything you want it to...
When I use a script with nested procs, and loops, and far down in the stack I call 'eval vlog ...', and the vlog fails, Modelsim will NOT continue from where it failed. This is pretty useless to me as I don't need it to continue further on, but right there. So one file failed vlog, continue compiling the rest! The solution is to 'catch' the error. Wrap 'eval vlog' in a catch. Something like 'catch [eval vlog ...]'. You can also use onerror to perform any error level logging and stuff, but this way it will still continue right where you were.
argv doesn't exist
As most people know, and as is written up on the web a lot, Modelsim hijacks argv. They give you argc and all the arguments appear as %1 ... %n where n is argc. argv is set to '-gui', and that's it.
quietly / transcript off | on | quiet ...
Modelsim by default echoes all set commands to the console. This is b/c of Modelsim's transcript setting. You can either call 'quietly set ...' or just set 'transcript off' at the top. echo will stop working, but you shouldn't use that anyhow, use 'puts ...' in place of 'echo ...' for compatibility across operating systems.
onerror {resume} ... doesn't actually do everything you want it to...
When I use a script with nested procs, and loops, and far down in the stack I call 'eval vlog ...', and the vlog fails, Modelsim will NOT continue from where it failed. This is pretty useless to me as I don't need it to continue further on, but right there. So one file failed vlog, continue compiling the rest! The solution is to 'catch' the error. Wrap 'eval vlog' in a catch. Something like 'catch [eval vlog ...]'. You can also use onerror to perform any error level logging and stuff, but this way it will still continue right where you were.
argv doesn't exist
As most people know, and as is written up on the web a lot, Modelsim hijacks argv. They give you argc and all the arguments appear as %1 ... %n where n is argc. argv is set to '-gui', and that's it.
Wednesday, July 21, 2010
Altera - Ease of use leader? Not on your life!
I've used Altera a few times in the last 10 years. Wrote a few FPGAs, used Quartus, SignalTap, and Megawizard. But I've never had the chance to really compare Xilinx and Altera. Now I've actually had the opportunity to do a side by side comparison of Xilinx's and Altera's PCI Express cores.
Here it is: I was comparing Xilinx and Altera's PCIe root complexes to see which one simulates faster. First challenge was to create the cores, and learn how to instantiate them. I used Virtex 6 and Stratix IV as the FPGAs to instantiate for. I used the vendor provided cores, and not the 3rd party ones. I started with Xilinx.
Xilinx's instantiation wizards are a bit ugly, but they create the core well enough. For Xilinx I started by checking if there were any compile time defines I needed, and it looked like I didn't need anything special. So I found the top level file for the core, and started to manually instantiate it in a basic top_tb.v file. I then started to compile just to see what I'd need to get the simulation moving. There were a bunch (if not all) of the files in one directory which I needed, and I also needed the secureip and unisims libraries. Once I got the simulation to load, I went in to my top_tb, and added in the reference clock as needed. I then saw the tx p/n ports start wiggling as expected. For Xilinx it was easy to see that I needed a parameter to put it into simulation mode - a mode which shortens certain processes in the physical level training algorithm. At this point, I connected my endpoint and saw the link come up very quickly.
Now on to Altera.
Altera's instantiation wizards are much nicer. They also create the core as expected. I began in the same way as Xilinx by going through and checking if I needed special compile time defines. Like Xilinx, there weren't any. What I did notice which surprised me was the use of defparams, but that's not a problem b/c it's inside their code and doesn't affect me. I now took the top level module and began instantiating it in a new top_tb.v. Altera's core required me to compile altera_mf, sgate, 200model, stratixiv_hssi_atoms, stratixiv_pcie_hip, and some others which I can't recall at the moment. I also had to compile a number of files that were in the core's tree. Once I got all the modules needed to load the simulation, I was able to run. Altera's core loads up with a large number of mis-matched port connections. Altera's core didn't bother to connect many output ports in many places (presumably b/c they weren't using them). They also connected some ports with incorrect sizes. This fills the Modelsim console with a bunch of warnings. At this point I added the clock to top_tb.v, and set the lowest bit of test_in to 1 as appropriate for simulations. I expected to quickly see tx p/n to start wiggling. I saw the tx p/n pins wiggle after a very long time. I didn't bother to connect the endpoint.
Now for the important stuff:
Altera's simulations are slow as molasses!
Altera's PCIe core runs many times slower than Xilinx's or Lattice's. I have run all three and I was shocked. Truth is at first I thought Altera's core wasn't working until I realized that I had to give it more time. That is how slow it was. At least 2x slower if not closer to 4x slower than either of the other 2 vendors that I've worked with.
Altera's core is confusing and unintuitive!
I was trying to find the signal that says that the transaction layer is ready. Something like Lattice's dl_up, and Xilinx's trn_dl_up. There is no such thing in Altera's core. To see that Altera's core is up you must look at the ltssm status. The core provides multiple clocks that you need to set, many different resets, a test_in and test_out array, and in general lots of signals that are shockingly unclear.
Altera's port list is very old style, and separates the ports based on input and output!
The top level port list is old Verilog style. I don't have any problem with that of course. What is disconcerting is that the list has 2 sections, inputs and outputs. This is completely unhelpful. Xilinx's core has the ports separated by function: Common, TX, RX, CFG ... This is how I would have liked Altera to do it. Perhaps there's a reason for this, but it seems to follow the same paradigm of providing a very unfriendly RTL.
Altera's provided simulation is a mess!
I tried to understand the paradigm of Altera's PCIe core by looking at the simulation, and I can only use the word appalled. The method they use to reset the simulation, is some form of counter at which point they start things running. I was looking for this to find Altera's recommended method of seeing if the transaction layer is up. There was nothing to find.
I finally gave up on Altera, not because I couldn't get it working, but because it was not nearly worth the effort. Their core is a huge mess. I'm sure it works well enough in synthesis, but I wouldn't bother trying to run it in simulation. Even when using their Modelsim script to run simulation, you get the Modelsim warnings of unconnected ports. They should be embarrassed to release something like this. If only their IP team that provides these cores could learn from the GUI team. Very nice UI, horrible RTL.
I'm sure I could go on and on, but suffice it to say that for the advanced user who likes to read the code created, Xilinx is much better. Also for anyone who desires to simulate efficiently, Xilinx is much better. I have never been so shocked by a vendor before, especially one that is purported to be the most user friendly of the vendors. Both Lattice and Xilinx win against Altera.
As a side-note relating to Altera, it seems these types of issues have existed long before their PCI Express core. I have spoken with some very long-time Altera users, and it is known that their FIFOs simulate horrifically slow, and their old PCI core user-side was a mess too. Perhaps its time for Altera to heed this call and take the necessary steps to remedy this situation.
Noah
Here it is: I was comparing Xilinx and Altera's PCIe root complexes to see which one simulates faster. First challenge was to create the cores, and learn how to instantiate them. I used Virtex 6 and Stratix IV as the FPGAs to instantiate for. I used the vendor provided cores, and not the 3rd party ones. I started with Xilinx.
Xilinx's instantiation wizards are a bit ugly, but they create the core well enough. For Xilinx I started by checking if there were any compile time defines I needed, and it looked like I didn't need anything special. So I found the top level file for the core, and started to manually instantiate it in a basic top_tb.v file. I then started to compile just to see what I'd need to get the simulation moving. There were a bunch (if not all) of the files in one directory which I needed, and I also needed the secureip and unisims libraries. Once I got the simulation to load, I went in to my top_tb, and added in the reference clock as needed. I then saw the tx p/n ports start wiggling as expected. For Xilinx it was easy to see that I needed a parameter to put it into simulation mode - a mode which shortens certain processes in the physical level training algorithm. At this point, I connected my endpoint and saw the link come up very quickly.
Now on to Altera.
Altera's instantiation wizards are much nicer. They also create the core as expected. I began in the same way as Xilinx by going through and checking if I needed special compile time defines. Like Xilinx, there weren't any. What I did notice which surprised me was the use of defparams, but that's not a problem b/c it's inside their code and doesn't affect me. I now took the top level module and began instantiating it in a new top_tb.v. Altera's core required me to compile altera_mf, sgate, 200model, stratixiv_hssi_atoms, stratixiv_pcie_hip, and some others which I can't recall at the moment. I also had to compile a number of files that were in the core's tree. Once I got all the modules needed to load the simulation, I was able to run. Altera's core loads up with a large number of mis-matched port connections. Altera's core didn't bother to connect many output ports in many places (presumably b/c they weren't using them). They also connected some ports with incorrect sizes. This fills the Modelsim console with a bunch of warnings. At this point I added the clock to top_tb.v, and set the lowest bit of test_in to 1 as appropriate for simulations. I expected to quickly see tx p/n to start wiggling. I saw the tx p/n pins wiggle after a very long time. I didn't bother to connect the endpoint.
Now for the important stuff:
Altera's simulations are slow as molasses!
Altera's PCIe core runs many times slower than Xilinx's or Lattice's. I have run all three and I was shocked. Truth is at first I thought Altera's core wasn't working until I realized that I had to give it more time. That is how slow it was. At least 2x slower if not closer to 4x slower than either of the other 2 vendors that I've worked with.
Altera's core is confusing and unintuitive!
I was trying to find the signal that says that the transaction layer is ready. Something like Lattice's dl_up, and Xilinx's trn_dl_up. There is no such thing in Altera's core. To see that Altera's core is up you must look at the ltssm status. The core provides multiple clocks that you need to set, many different resets, a test_in and test_out array, and in general lots of signals that are shockingly unclear.
Altera's port list is very old style, and separates the ports based on input and output!
The top level port list is old Verilog style. I don't have any problem with that of course. What is disconcerting is that the list has 2 sections, inputs and outputs. This is completely unhelpful. Xilinx's core has the ports separated by function: Common, TX, RX, CFG ... This is how I would have liked Altera to do it. Perhaps there's a reason for this, but it seems to follow the same paradigm of providing a very unfriendly RTL.
Altera's provided simulation is a mess!
I tried to understand the paradigm of Altera's PCIe core by looking at the simulation, and I can only use the word appalled. The method they use to reset the simulation, is some form of counter at which point they start things running. I was looking for this to find Altera's recommended method of seeing if the transaction layer is up. There was nothing to find.
I finally gave up on Altera, not because I couldn't get it working, but because it was not nearly worth the effort. Their core is a huge mess. I'm sure it works well enough in synthesis, but I wouldn't bother trying to run it in simulation. Even when using their Modelsim script to run simulation, you get the Modelsim warnings of unconnected ports. They should be embarrassed to release something like this. If only their IP team that provides these cores could learn from the GUI team. Very nice UI, horrible RTL.
I'm sure I could go on and on, but suffice it to say that for the advanced user who likes to read the code created, Xilinx is much better. Also for anyone who desires to simulate efficiently, Xilinx is much better. I have never been so shocked by a vendor before, especially one that is purported to be the most user friendly of the vendors. Both Lattice and Xilinx win against Altera.
As a side-note relating to Altera, it seems these types of issues have existed long before their PCI Express core. I have spoken with some very long-time Altera users, and it is known that their FIFOs simulate horrifically slow, and their old PCI core user-side was a mess too. Perhaps its time for Altera to heed this call and take the necessary steps to remedy this situation.
Noah
Subscribe to:
Posts (Atom)