I don't see a synchronizer on the rx data line.
All asynchronous inputs must be synchronized to the sampling clock. There are a couple of reasons for this: metastability and routing. These are different problems but are inter-related.
It takes time for signals to propagate through the FPGA fabric. The clock network inside the FPGA is designed to compensate for these "travel" delays so that all flip flops within the FPGA see the clock at the exact same moment. The normal routing network does not have this, and instead relies on the rule that all signals must be stable for a little bit of time before the clock changes and remain stable for a little bit of time after the clock changes. These little bits of time are known as the setup and hold times for a given flip flop. The place and route component of the toolchain has a very good understanding of the routing delays for the specific device and makes a basic assumption that a signal does not violate the setup and hold times of the flip flops in the FPGA. With that assumption and knowledge (and a timing constraints file) it can properly place the logic within the FPGA and ensure that all the logic that looks at a given signal sees the same value at every clock tick.
When you have signals that are not synchronized to the sampling clock you can end up in the situation where one flip flop sees the "old" value of a signal since the new value has not had time to propagate over. Now you're in the undesirable situation where logic looking at the same signal sees two different values. This can cause wrong operation, crashed state machines and all kinds of hard to diagnose havoc.
The other reason why you must synchronize all your input signals is something called metastability. There are volumes written on this subject but in a nutshell, digital logic circuitry is at its most basic level an analog circuit. When your clock line rises the state of the input line is captured and if that input is not a stable high or low level at that time, an unknown "in-between" value can be captured by the sampling flip flop.
As you know, FPGAs are digital beasts and do not react well to a signal that is neither high nor low. Worse, if that indeterminate value makes its way past the sampling flip flop and into the FPGA it can cause all kinds of weirdness as larger portions of the logic now see an indeterminate value and try to make sense of it.
The solution is to synchronize the signal. At its most basic level this means you use a chain of flip flops to capture the input. Any metastable level that might have been captured by the first flip flop and managed to make it out gets another chance to be resolved before it hits your complex logic. Two flip flops are usually more than sufficient to synchronize inputs.
A basic synchronizer looks like this:
entity sync_2ff is
port (
async_in : in std_logic;
clk : in std_logic;
rst : in std_logic;
sync_out : out std_logic
);
end;
architecture a of sync_2ff is
begin
signal ff1, ff2: std_logic;
-- It's nice to let the synthesizer know what you're doing. Altera's way of doing it as follows:
ATTRIBUTE altera_attribute : string;
ATTRIBUTE altera_attribute OF ff1 : signal is "-name SYNCHRONIZER_IDENTIFICATION ""FORCED IF ASYNCHRONOUS""";
ATTRIBUTE altera_attribute OF a : architecture is "-name SDC_STATEMENT ""set_false_path -to *|sync_2ff:*|ff1 """;
-- also set the 'preserve' attribute to ff1 and ff2 so the synthesis tool doesn't optimize them away
ATTRIBUTE preserve: boolean;
ATTRIBUTE preserve OF ff1: signal IS true;
ATTRIBUTE preserve OF ff2: signal IS true;
synchronizer: process(clk, rst)
begin
if rst = '1' then
ff1 <= '0';
ff2 <= '0';
else if rising_edge(clk) then
ff1 <= async_in;
ff2 <= ff1;
sync_out <= ff2;
end if;
end process synchronizer;
end sync_2ff;
Connect the physical pin for the N64 controller's rx data line to the async_in input of the synchronizer, and connect the sync_out signal to your UART's rxd input.
Unsynchronized signals can cause weird issues. Make sure any input connected to an FPGA element that isn't synchronized to the clock of the process reading the signal is synchronized. This includes pushbuttons, UART 'rx' and 'cts' signals... anything that is not synchronized to the clock that the FPGA is using to sample the signal.
(An aside: I wrote the page at www.mixdown.ca/n64dev many years ago. I just realized that I broke the link when I last updated the site and will fix it in the morning when I'm back at a computer. I had no idea so many people used that page!)
I will leave it to an LRM expert to provide a more detailed answer, but in short, your approach should be valid - I ran a quick test with a recent version of Quartus, and it handles '-'
like it's supposed to - the logic generated is reduced as expected when the output is defaulted to '-'
('X'
works too, by the way). More on the approaches you listed:
Not assigning the signal isn't really an option for your example, of course, if you don't want latches. If it's a clocked process, you're slightly better off, but you'll still get enables where you might not need them. Maybe I'm missing your intent here.
'-'
, as previously noted, is probably the best option, for both semantic and practical reasons.
Depends on what you mean by "undefined". 'X'
is technically "unknown". 'U'
is for uninitialized signals, which ModelSim displays as "X"
for hex representations. 'X'
does seem to work, though, as I noted above.
Another alternative would be to do the optimization yourself and remove one case from being tested explicitly:
if instruction(15 downto 8) = "11111001" then
write_data <= std_ulogic_vector(resize(signed(instruction(7 downto 4)), 16));
else
write_data <= std_ulogic_vector(resize(signed(instruction(7 downto 0)), 16));
end if;
This has significant disadvantages (mostly related to code clarity), though, and I would probably opt for a more ideal solution.
Incidentally, '-'
is also commonly used with std_match()
, which I would consider using for your decoding, e.g.:
if std_match(instruction(15 downto 8), "1100----") then
Though at that point, you're probably better off just using case?
.
Best Answer
Many languages have scoping rules that treat variables defined in outer blocks as read-only in inner blocks. In the inner 'for', the attempt to write to a variable 'i' creates a new one that's in scope in the inner 'for' only. With a namespace per block, the compiler is not confused between outer.i and inner.i.
Even if it does work, it's certainly confusing for the programmer, and so should not be used. Is that report statement printing the final value from the inner loop, or the current value from the outer loop?
There's an annual competition to obfuscate C code, but just because you can write a program that works and looks wrong doesn't mean you should.
You will forget what you wrote in a few weeks' time, and anybody else seeing it will think 'wtf?'. If you write the hardware for a missile relying on this construct, and then the language gets updated at a later date to eliminate 'confusing' behaviour, then you'll be in trouble if it ever gets recompiled.