3
votes

I have a Verilog module that reads data off a 32-bit bus, one word at a time, and assembles the words into a wider reg that is the input to several other modules. The input bus is a memory bus, so every word comes with an address that identifies where I have to store it.

I use part select to store the words into the reg. Because the word at the lowest address on the bus corresponds to the most-significant word in the reg, I generate the offset for the part select by flipping the bits of the address and then appending three 0 bits to convert it from a byte offset to a bit offset.

This actually works just fine. But because I use the "flip bits then add three zeros" logic in several places, I decided to assign it to a wire, then use the wire as the offset in the part select. However, when I do this, suddenly it doesn't work at all. The first write to word 0 works. But then on the second cycle, the write to word 1 overwrites word 0! Then the following write to word 2 gets saved in word 1. It's as if the wire was a reg and that the results of calculating the offset were not seen until the next cycle. This seems pretty strange since I think of the wire as just being the lines of the address bus run through some NOT gates; whenever the address changes, the offset changes.

I distilled the problem down into the following two examples. I'm using a 3-bit address bus and 8-bit values just to make it easier to see what's going on. Can anyone explain why they exhibit different behavior? I assume that it's due to some idiosyncrasy with the way Verilog schedules blocks, but I can't explain it. Obviously I can just use the working version, or use an array instead of a wide reg, but I think that understanding the reason for this will help me in the future.

Thank you!

Works:

module busif(
        input clock,
        input [2:0] address,
        input read,
        input write,
        input [7:0] data_in,
        output reg [7:0] data_out
    );

    // 8 bytes of memory (3-bit address).
    reg [63:0] memory;

    always @(posedge clock) begin
        if (write) begin
            memory[{~address, 3'b000} +: 8] <= data_in;
        end
        // (read case omitted)
    end       

endmodule

Does not work (offset seems to take 1 cycle to propagate through the "wire"):

module busif(
        input clock,
        input [2:0] address,
        input read,
        input write,
        input [7:0] data_in,
        output reg [7:0] data_out
    );

    // 8 bytes of memory (3-bit address).
    reg [63:0] memory;

    wire [5:0] offset;
    assign offset = {~address, 3'b000};

    always @(posedge clock) begin
        if (write) begin
            memory[offset +: 8] <= data_in;
        end
        // (read case omitted)
    end       

endmodule
1
Uploading a waveform image with relevant signals displayed would probably help here. - Tim

1 Answers

2
votes

There's nothing wrong with your code - note that isim has historically been very buggy, and probably still is. I gave up with it a couple of years ago.

Simple testbench attached - this passes with both Modelsim and Icarus, for both your modules.

/**
 * Maia testbench: download and install maia from maia-eda.net. To run the
 * testbench, save this code as tb.tv, and:
 *
 * rtv tb.tv busif.v
 *
 * On success, you should get the following output:
 *
 * # finished: 'memory' is 102040810204080
 * # (Log) (80 ns) 8 vectors executed (8 passes, 0 fails)
 *
 */
#pragma _StrictChecking 0  // turn off some type checking for simplicity

DUT {
   module busif
      (input           clock,
      input [2:0]      address,
      input            read,
      input            write,
      input [7:0]      data_in,
      output reg [7:0] data_out);

   signal(output reg[63:0] memory);                // internal signals to test

   [clock, write, address, data_in] -> [memory];   // vector definition
   create_clock clock;                             // define a clock
}

main() {
   int   i;
   var64 expected = 64`hxxxxxxxx_xxxxxxxx;
   var64 mask     = 64`hff000000_00000000;
   var64 data;

   for(i=0; i<8; i++) {
      data = 64`h1 << ((7-i)*8) + i;
      expected &= ~mask;                           // clear out the new byte
      expected |= data;                            // get the expected result
      mask >>= 8;

      [.C, 1, i, 1<<i] -> [expected];              // clock and test
   }
   report("finished: 'memory' is %x\n", memory);   // read this backwards!
}