debugging

Fall 2023

You are not logged in.

Please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.mit.edu) to authenticate, and then you will be redirected back to this page.

Hey all, Over the last couple of weeks we've noticed a few recurring patterns that can cause your designs to work fine when you test bench them, but then break when you try and implement onto the FPGA. I wanted to do a quick write-up on what some of these things are and how you might fix them, assuming that you have a working simulation that tests everything you want to test. (@other instructors feel free to add to this document! am definitely missing stuff) - Obviously, multi-driven net critical warnings in your build logs are bad, so check for these. This may cause a build failure or things may silently work out only to produce unanticipated behavior. Getting an error like this means you're trying to drive the value of some net (trying to drive a wire, or latch a new value into a register) in two separate always blocks. Doesn't matter if you've got a conditional on one of them so theoretically the conflicting assignments wouldn't happen at the same time! The build log should show which variables are triggering the warning. - Inferred latch warnings are usually a result of you trying to save state in a combinational block, and generally should be avoided / caused a lot of unpredictable behavior in lab 5. For example, if I have code like this


always_comb begin
   if (rst) axiov = 0;
   else if (some_condition) axiov = 1;
end

...what's axiov supposed to be when reset isn't asserted and some_condition isn't met? Software engineers (and the Verilog spec) would probably claim that axiov should be whatever it was the previous cycle, but in hardware upholding this would mean that axiov needs to store some state - and since we're in the combinational world, we need a latch to hold that state. If you see one in your build log, make sure that your always_comb blocks assign variables exhaustively to get rid of it, i.e. in all cases. For example,


always_comb begin
    if (rst) axiov = 0;
    else if (some_condition) axiov = 1;

    /* need the below else statement to be exhaustive for axiov!
     * this doesn't match our original design intention
     * for axiov, which was stateful. so we should probably
     * take axiov and make it a register we assign in an
     * always_ff block so that it can hold state
     */
    else axiov = // something else.
end

- Complex indexing schemes can get super messy and are hard for the staff to debug. They're also hard for you to corner-case-test in simulation, so you shouldn't use them. If you're getting a failure in real hardware and can't figure out why, try using shifting intelligently to reduce the amount of indexing you need to do. For example, in lab 5's firewall, instead of sliding through the source MAC address with something like this:


if ({my_mac[48-i], my_mac[48-i-1]} == axiid) begin ...

You can copy my_mac into a shift buffer and reduce complexity considerably:


if (my_mac_sb[48:47] == axiid) begin ...
my_mac_sb <= my_mac_sb << 2;

I also made another key mistake in the example code above... - Never _ever_ use the result of a subtraction in an unsigned comparison operation, or to index into some array. Integer underflow errors are a sneaky hot mess to deal with and you might not catch all of them when you go and debug in simulation, so it's probably best to just ensure they can't possibly happen to begin with. This was an issue with some lab 5 implementations and definitely an issue in parts of lab 3 and 4 - for instance, instead of doing


if (a - 2 > b) ... // may underflow on a if a < 2

do


if (a > b + 2)
// won't underflow, but make sure b is wide enough to take +2!

- When you're simulating, make sure to run a few cases (or all of your cases, preferably, but use your best judgement) where you don't reset your module between different sets of stimuli / test cases. This ensures that any state machines can handle multiple inputs back to back, which is how things will be in the real world. A complete test bench will also wait varying amounts of time between tests as necessary. I'll update this more as I come up with more easy checks, but hopefully this saves you all some debugging time.