If you are wondering about how the <= operator works; it is what is called a 'nonblocking assignment.' What this means is that the left hand side of all of the <= are performed for a particular event (e.g. rising clock edge) and then once those are all evaluated, the result is placed in the output. This allows you to write shift registers without temporary variables. None of the values change until the left hand calculations are completed, then the results are moved over to the right hand side. It is generally not a good idea to use nonblocking assignments for combinatorial logic. Generally they are only used to create latches and registers and you use regular blocking assignments for combinatorial logic. When synchronized with a clock signal, you can generally consider <= operations to be D flip-flops that sample the input and transfer it to the output atomically on a single clock edge.
'Race conditions' where intermediate indeterminate results appear on the outputs of combinatorial functions are hard to avoid and they can depend greatly on how the design is actually implemented on an ASIC or FPGA. However, most designs are synchronous and so as long as the output settles within one clock period this is not a problem. There are tools that can check the timing performance of a design to check all of the path delays to ensure that the results will always be valid for a given clock frequency, but this is highly dependent not on the actual HDL code but on the way the design is placed and routed.
Synthesizers (not compilers!!!!) will generally perform optimization on combinatorial logic. There are limits to how much the synthesizer can do (e.g. it will not re-architect your system) so you have to know more or less how it will end up being implemented. If you're working on an FPGA, generally the synthesis and place and route will pack any logic function that fits onto LUTs. So if you can separate out a single logic function with up to 4 inputs and 1 output, this will end up on a single LUT and the only delay that matters is the propagation delay of the LUT, which is the same for all of its inputs. In the case of your example function, both pieces of code may be implemented identically on one LUT with three inputs and one output.
A simple solution would be to use a Mealy Machine. Here, you would only intercept the data user_w_write_8_data
which will be written to the FIFO. All control signals associated with the write side should be left as in the original XillyBus demo. Thus, the instantiation of your STD_FIFO
will be:
my_fifo : STD_FIFO
port map(
CLK => bus_clk,
RST => reset_8,
DataIn => din, -- modified data to write
WriteEn => user_w_write_8_wren, -- as in original demo!
ReadEn => user_r_read_8_rden,
DataOut => user_r_read_8_data,
Full => user_w_write_8_full, -- as in original demo!
Empty => user_r_read_8_empty
);
Instead of STD_FIFO
you can also use the component fifo_8x2048
of the XillyBus demo.
The Mealy Machine will then have a counter which is incremented with every write. Whenever the counter state is 0, the write data user_w_write_8_data
is modified and assigned to signal din
which is connected to the FIFO. In all other cases, the original value of user_w_write_8_data
is assigned to din
.
Thus, the code of the clocked process would look like this:
process (bus_clk)
begin
if (bus_clk'event and bus_clk = '1') then
if (reset_8 = '1') then
counter <= 0;
elsif (user_w_write_8_wren='1') then
if counter = 7 then counter <= 0;
else counter <= counter + 1;
end if;
end if;
end if;
end process;
The Mealy Machine will have another process which defines the output for the current cycle based on the current state of the counter (or any other state variable). You must list all signals in any expression in the process sensitivity list. You must also assign a value to every output in every control path, otherwise you will get unintended latches. The latter is ensured by assigning a default value at the beginning of the process and overwrite it later on. Always the last assignment (before a wait
) wins.
process(counter, user_w_write_8_data)
begin
-- This is the default assignment which might be overriden below.
din <= user_w_write_8_data;
if counter=0 then
--From upper case to lower case
if (user_w_write_8_data="01000001") then --A
din(5) <= not user_w_write_8_data(5);
elsif (user_w_write_8_data="01000010") then --B
--
-- And so on
--
end if;
end if;
end process;
Best Answer
I have done a type of fifo with had four extra inputs: rd_cancel and rd_commit, and wr_cancel and wr_commit. The idea is that you can do a bunch of writes and then either cancel the writes you did (erase the data you just wrote) or commit that data (make it available to the read port). The same thing for reading: rd_cancel would "unread" what you just read, rd_commit would commit your reads. The cancel/commit operation would cancel or commit only the data since the last cancel/commit.
This is done by having a temporary copy of the write and read pointers. These temp pointers are copied back and forth with the real pointers depending on the cancel/commit inputs. Also, when calculating if the FIFO is full or not, sometimes the temp pointers or the real pointers are used. It's a bit complicated, and beyond this kind of forum, but if this sounds like what you want then let me know and I'll see what I can come up with.
Just FYI: I have never seen this type of FIFO done before, but it seems like an obvious solution to several FIFO related problems. The last time I googled for a FIFO like this I came up empty, but I'd be surprised if nobody else have ever dreamed up something like this.