Thursday, March 31, 2011

Building an SPI interface... (part 2)

Now that we have a flexible clock generator the next thing to consider is the detection of signals for the clock/MISO and MOSI bus lines...

We know that the important times to receive or transmit information are the clock edges (both positive/rising and negative/falling).
We also know that the intervals between these edges (when the signal on the clock is stable) are the times we can setup to transmit on MISO or MOSI (depending on whether we are acting as a master or a slave.  The easiest way to do this is to use the clock edges to change the state of a registers that indicate the states of MISO and MOSI at the positive and negative edges... This way the state is available until it changes again (remember that when the clock is stable MISO and MOSI can change so it's not a good idea to use them directly).  If we also add registers for the positive and negative edges we can directly trigger other modules that new information is available (it's much easier to get the timing right using a register for the clock rather than using it directly).

Like most modules the hdl is easy in verilog (you probably should start to realize now that if the hdl is hard to understand you probably have a more complicated solution than is needed, or you may want to separate the design into multiple modules).

// below here we are not in the clock domain - need to see things not in sync with clock   
   always @ (posedge spi_clock) begin
      busdetect_spi_pos <= ~busdetect_spi_pos;
      if (spi_mosi == 1'b0)
        busdetect_mosi0 <= 1'b0;                               
      else if (spi_mosi != 1'b0)
        busdetect_mosi0 <= 1'b1;
      if (spi_miso == 1'b0)
        busdetect_miso0 <= 1'b0;                               
      else if (spi_miso != 1'b0)
        busdetect_miso0 <= 1'b1;
   end // read0 signals detect    
  
   always @ (negedge spi_clock) begin
      busdetect_spi_neg <= ~busdetect_spi_neg;
      if (spi_mosi == 1'b0)
        busdetect_mosi1 <= 1'b0;                               
      else if (spi_mosi != 1'b0)
        busdetect_mosi1 <= 1'b1;
      if (spi_miso == 1'b0)
        busdetect_miso1 <= 1'b0;                               
      else if (spi_miso != 1'b0)
        busdetect_miso1 <= 1'b1;
   end // read1 signals detect    
As usual, all of the full verilog files can be found here.  

Next we will build upon these to extract and transmit data from the bus.

Wednesday, March 30, 2011

Building an SPI interface... (part 1)

The SPI interface is a very useful way to transfer data from one device to another.  Previously we looked at the TWI/I2C bus (which is great for having multiple devices connected to a serial bus and share both the clock and data lines) those devices have unique addresses and data is transferred in bytes (8 bits at a time).  The SPI bus connects each device to three lines (a clock, MISO - master in slave out and MOSI - master out slave in).  Here the intended device has it's own unique enable line (which gets rid of the protocol overhead of addressing devices at the cost of more wires on the board).  There is no requirement for the speed, the number of bits transferred or whether the master or slave has to send data on each clock pulse - in practice it's a much more quicker and efficient way to move data.

With SPI the master is responsible for generating the clock and because the lines are usually true rather than open collector/open drain the slave can't do anything to slow the clock down.  The clock line has two important characteristics - Polarity and Phase - which together determine when the master and slave set and read the MOSI and MISO lines and can be grouped into 4 modes. 
  • mode 0 write on falling clock read on rising clock
  • mode 1 write on rising clock read on falling clock
  • mode 2 write on rising clock read on falling clock
  • mode 3 write on falling clock read on rising clock
This is from the point of view of the master (remember that the master is the device creating the clock).  The key things to remember is that the master needs to generate the clock when it wants to either read from the slave or write to the slave.  It is important to know the mode (polarity and phase) because the clock needs to be held in the right state when the master doesn't want to transmit or receive.  I am adding the ability to tristate the clock line so that a hybrid master/slave or a backup master can be physically wired.  Since the SPI bus can run at multiple speeds and these speeds may be different each of the devices sharing the bus I am adding an 8 bit interface for setting the clock divider.  The hdl isn't that hard in verilog.
assign spi_clock = (spi_clock_enable) ? spi_clock_write : 1'bz;

   // This part drives spi_clock
   // write is toggled on the delay counter, use enable elsewhere to
   // take the line hot.
   always @ (posedge clock) begin
      if (reset) begin
         // reset all the control registers/delays
         spi_clock_delay  <= clock_divider;        
         spi_clock_enable <= 1'b0;
         push_intent      <= 1'b0;
      end
      else if (!reset) begin   
         // here we will run the spi_clock with high/low intervals
         if (!slave) begin
            spi_clock_enable <= (rec_intent || send_intent || spi_clock_live);
         end
         else if (slave) begin
            spi_clock_enable <= 1'b0;
         end
                       
         if (!push_intent) begin
            if (rec_intent || send_intent) begin
               push_intent <= 1'b1;
            end
         end
         else if (push_intent) begin
            if (!rec_intent && !send_intent) begin
               if (polarity == 1'b0 && phase == 1'b0 && busdetect_spi) begin
                  // mode 0 write on falling clock read on rising clock
                  push_intent <= 1'b0;
               end  
               else if (polarity == 1'b0 && phase == 1'b1 && !busdetect_spi) begin
                  // mode 1 write on rising clock read on falling clock
                  push_intent <= 1'b0;
               end  
               else if (polarity == 1'b1 && phase == 1'b0 && !busdetect_spi) begin
                  // mode 2 write on rising clock read on falling clock
                  push_intent <= 1'b0;
               end  
               else if (polarity == 1'b1 && phase == 1'b1 && busdetect_spi) begin
                  // mode 3 write on falling clock read on rising clock
                  push_intent <= 1'b0;
               end  
            end
         end
                               
         if (spi_clock_delay == 0 && (push_intent) && spi_clock_enable) begin
            // spi_clock flip state
            spi_clock_delay <= clock_divider;        
            spi_clock_write <= ~spi_clock_write;
         end
         else if (spi_clock_delay > 0 && (push_intent) && spi_clock_enable) begin
            spi_clock_delay <= spi_clock_delay - 1;
         end               
         else if (!spi_clock_live && !push_intent) begin
            // set polarity prior to enable so we start in a happy state
            spi_clock_write <= polarity;
            spi_clock_delay <= clock_divider;
         end   
                       
      end // clock not in reset so drive spi_clock           
   end // posedge clock - spi_clock or hard reset

To give you a quick idea of how this module works, here's what the output looks like for most of the input combinations.  The entire verilog files can be found here. 

Monday, March 28, 2011

Why I like some companies...

I have a few projects underway (unrelated to work) that require a considerable amount of time learning new concepts, filling some holes in my education, and lots of time doing bench testing and building prototypes.  It's frustrating at times, but overall it's a fun way to spend the small amount of free time I have on a given day.

I'm one of those people that can only really be happy when there are challenges and opportunities to learn new new things each day.  These types of projects tend to progress in waves, it's not a linear process - advancement is unpredictable...  For these projects I have limited time on a given day (but no deadline - so time isn't really a factor) - I have limited funds (so I have to be careful to not waste money) - but I have unrestricted motivation (which means I can work on whatever I choose).  What I have realized is that just like the field in which I work, there are huge differences in the electronics companies...

Some companies really deserve to exist and prosper.  These companies behave as if they don't just want to exist and make money, but rather as if they care about the quality of the products they make and their long term interactions with the reset of the community.  They all have different characters, but they share these qualities:
  1. They make products that actually do what they are supposed to...
  2. They provide plenty of documentation that is relevant to understanding how the device works...
  3. They provide an explanation for why specific design choices were made over others...
  4. They provide not just examples of typical device usage, but also examples of why certain implementations are not as good and explanations for improvement...
  5. They don't just compare statistics about their products to that of a competitor, but rather explain when the devices are interchangeable and when one may be a better choice.
  6. They are available (phone/email) to answer questions and advise how to approach the problems the develop.
  7. They make products that are actually useful rather than something that just makes good marketing opportunities.
  8. They get the tools (sometimes device samples/sometimes the development software/always the documentation) into people's hands to promote familiarity/testing/comparisons with their products.
  9. They have a solid supply chain so the products are reasonably available.
  10. They treat everyone well and don't just cater to current customers.

The important thing is that all of these companies have the goal of making money.  That's a good thing - it's why they can make what they do - it's why they can make better things in the future - it's why they can hire people.  The differences really come down to whether a given company wants to be a leader in the industry or whether it just wants to grab enough scraps to get by...

These companies (in my humble opinion) are the best I have dealt with - they cover at least 9 out of the 10 points above... These companies are always my preferred choice as not just a customer, but also to recommend to others. (in alphabetical order because they all are sufficiently unique that they can't be directly compared).

Analog Devices
Fairchild semiconductor
Linear Technologies
Maxim ic
Microchip
National semiconductor
On semiconductor 
ST microelectronics
Texas Instruments
Xilinx

These companies are not yet at the top of the pack - they cover some of the points above - they have the potential (but not yet the ability) to cover the rest.  I have had good experiences with them and have hope that they can become leaders as they grow. (in alphabetical order because they all are sufficiently unique that they can't be directly compared).

Lattice semiconductor
Micrel
NXP
Zilog

I'm not mentioning any of the many other companies I have dealt with in the past that I wouldn't rank as excellent.  Some of them have unique products and so I will buy from them when necessary.  Some I won't use.  There are far more companies that I have not used because I have not needed what they make so don't assume anything about companies not listed above.  It's also important to remember that as time has passed some of the great companies from the past have been broken into multiple smaller companies and others have merged and been absorbed as well.  Luckily none of my favorites over the years have been ruined in the process (but great names have been lost to history).

This is just my opinion - doubtful that anyone cares - but I really do think that it's important to consider more than just how shiny and glittery a product is before you buy.  Companies that you feel are leaders, companies that strive for excellence, companies that offer more than a product for cash really do deserve to exist.

Thursday, March 24, 2011

Remembering classic logic... (part 10)

Recently I pulled out my boxes of 7400 series logic (working on a few old school projects that were idled for a bit).  There's something special about these chips...

So, on a whim I thought that I'd bang out verilog modules for the chips I have on hand.  I'm putting the files for these modules in the tanukifu project here.


I finished up a number of the odd chips I had in the box but I'll mention a few here.


The 74x521 is a very simple chip that just compares two 8 bit inputs and if they are the same (bit for bit) then it outputs low and otherwise it output high.  Very simple to code in verilog...

  always @ (*) begin
      if (A == B) begin
         Q <= 1'b0;
      end
      else begin
         Q <= 1'b1;
      end
   end
 The 74x280 is much more interesting... This chip looks at 9 input lines and if there is an odd number of high bits then it outputs odd and if the number of high bits is even it outputs even.  This is very useful in determining whether a bit was corrupted in flight (although there are much better parity algorithms when you know something about the type of data or there are many more bits than 9.

It's not too hard to figure out how to design this one... there are a number of ways to do it, but I like symmetry and I like verilog that's easy to understand.


   // If you have 2 inputs then an exclusive or would let you know the parity - 01 and 10.
   // If you have 3 inputs then it's a bit harder - 001, 010, 100 are the cases. Which can be
   // thought of as (!A & !B & C) or (!A & B & !C) or (A & !B & !C).  Since we have 9 inputs
   // we can then use the same mechanism to compare the results of the first 3 triplets as triplets.
   assign J    = (!A & !B & C) | (!A & B & !C) | (A & !B & !C) | (A & B & C);
   assign K    = (!D & !E & F) | (!D & E & !F) | (D & !E & !F) | (D & E & F);
   assign L    = (!G & !H & I) | (!G & H & !I) | (G & !H & !I) | (G & H & I);  
   assign odd  = (!J & !K & L) | (!J & K & !L) | (J & !K & !L) | (J & K & L);
   assign even = ~odd;
This gives you a quick idea of what the mapping looks like (just counting through the progression in binary).

Looking only at the first part you can see the unfolded bits of the count below and the numerical representation under the outputs (which is a nice way to check that it works correctly).
       
The last one I'll mention here is the 74x249.  This is an open collector (it either grounds an output or let's it float in high impedance) that takes a 4 bit bcd input and maps it to the parts of a 7 segment display... there are lots of ways to drive 7 segment displays and usually I use a little microcontroller now, but these little chips were a very reliable way to get the job done... (other variants with true outputs can be used to supply current if your display is setup that way).


   always @ (*) begin
      if (!lt) begin
     dataout = 7'b0000000;
      end
      else if (lt & (!rbo | (!rbi & (datain == 4'b0000)))) begin
     // if rbo is low or rbi is low and the data in is 0000 then off
     dataout = 7'bzzzzzzz;
      end
      else if (lt & rbo) begin
     case (datain)
       (4'b0000): begin
          dataout = 7'b000000z;
       end
       (4'b0001): begin
          dataout = 7'bz00zzzz;
       end
       (4'b0010): begin
          dataout = 7'b00z00z0;
       end
       (4'b0011): begin
          dataout = 7'b0000zz0;
       end
       (4'b0100): begin
          dataout = 7'bz00zz00;
       end
       (4'b0101): begin
          dataout = 7'b0z00z00;
       end
       (4'b0110): begin
          dataout = 7'b0z00000;
       end
       (4'b0111): begin
          dataout = 7'b000zzzz;
       end
       (4'b1000): begin
          dataout = 7'b0000000;
       end
       (4'b1001): begin
          dataout = 7'b0000z00;
       end
       (4'b1010): begin
          dataout = 7'bzzz00z0;
       end
       (4'b1011): begin
          dataout = 7'bzz00zz0;
       end
       (4'b1100): begin
          dataout = 7'bz0zzz00;
       end
       (4'b1101): begin
          dataout = 7'b0zz0z00;
       end
       (4'b1110): begin
          dataout = 7'bzzz0000;
       end
       (4'b1111): begin
          dataout = 7'bzzzzzzz;
       end      
     endcase  
      end     
   end

That would be easier if it was binary rather than bcd, but the idea behind the 7 segment was to display base 10 data (0123456789) and bcd is a very common way to handle this; however,  incomplete symbol set mapping to the field space available always makes things uglier to handle than when the mapping is unique and complete...

That's about it for the classic logic I have available... I think next we will look at spi interfaces...

Wednesday, March 23, 2011

Remembering classic logic... (part 9)

Recently I pulled out my boxes of 7400 series logic (working on a few old school projects that were idled for a bit).  There's something special about these chips...

So, on a whim I thought that I'd bang out verilog modules for the chips I have on hand.  I'm putting the files for these modules in the tanukifu project here.

There are a lot of times one wants to step through a sequence of events, cause a certain number of events to occur or wait until a certain number of events have occurred before doing something... for these things counters are nice to have available.  If you chain flip-flops/registers together so that the output from one triggers the input clock on the next in line we can count easily in base two. 

The 74x90 is a BCD counter - oftentimes we need to convert to base 10 (because people commonly use base 10) and one way to do that is to combine a counter that goes to 5 and a second counter that goes to 2 - depending on how it's wired up we can have a 1:2 counter/divider and a 1:5 counter/divider or a 1:10 counter/divider.

The hdl in verilog for this can be done in a number of ways, but I'm doing it here in a way where it's easy to see how one state changes to the next (but this isn't an efficient way)...


   // the div2 section is tied to the Q0 output Aset goes high if the async set/reset triggers occur
   // but otherwise toggle based on the clk0.
   always @ (negedge clk0 or posedge Aset) begin
      if (Aset) begin
         // if preset or clear states indicate we should set intent
         Qint0 <= Q0;
      end
      else if (!Aset) begin
     // toggle state
         Qint0 <= ~Qint0; 
      end
   end

   // the div5 section is tied to clk1
   always @ (negedge clk1 or posedge Aset) begin
      if (Aset) begin
         // if preset or clear states indicate we should set intent
         Qint1 <= Q1;
         Qint2 <= Q2;
         Qint3 <= Q3;
      end
      else if (!Aset) begin
     // increase count to 5 then cycle over
     if (!Qint3 & !Qint2 & !Qint1) begin
        Qint1 <= 1'b1;
        Qint2 <= 1'b0;
        Qint3 <= 1'b0;       
     end
     else if (!Qint3 & !Qint2 &  Qint1) begin
        Qint1 <= 1'b0;
        Qint2 <= 1'b1;
        Qint3 <= 1'b0;       
     end
     else if (!Qint3 &  Qint2 & !Qint1) begin
        Qint1 <= 1'b1;
        Qint2 <= 1'b1;
        Qint3 <= 1'b0;       
     end
     else if (!Qint3 &  Qint2 &  Qint1) begin
        Qint1 <= 1'b0;
        Qint2 <= 1'b0;
        Qint3 <= 1'b1;       
     end
     else begin
        Qint1 <= 1'b0;
        Qint2 <= 1'b0;
        Qint3 <= 1'b0;
     end
      end
   end
  
   // async. set and clear logic, Aset is used to pass the async signals back to the output register
   // state determination.
   always @ (*) begin  
      if (!(reset0 & reset1) & (set0 & set1)) begin
         // set output high (1001)
         Q0   <= 1'b1;
         Q1   <= 1'b0;
         Q2   <= 1'b0;
         Q3   <= 1'b1;
         Aset <= 1'b1;
      end
      else if (reset0 & reset1) begin  
         // clear output
         Q0   <= 1'b0;
         Q1   <= 1'b0;
         Q2   <= 1'b0;
         Q3   <= 1'b0;
         Aset <= 1'b1;
      end
      else begin
         // change state
         Q0    <= Qint0;
         Q1    <= Qint1;
         Q2    <= Qint2;
         Q3    <= Qint3;
         Aset  <= 1'b0;
      end
   end // always @ (*)
If instead of base 10 one wanted just a 4 bit counter it's easier to describe in verilog because we can imply the connections between the registers. 

This is a 74x161... it's actually a bit more complicated than the 74x90 because it allow parallel loading of the registers and includes a terminal count output that makes it easier to chain multiple counters together... The 74x160 and 74x162 are BCD counters and the 74x161 and 74x163 are binary counters.  The 74x160 and 74x161 have async. resets and the 74x162 and 74x163 have clock sync. resets.

   // assign for terminal count output
   assign TC = Q & 4'b1111; // state 1111
 
   always @ (posedge clock or negedge reset) begin
      if (!reset) begin
         Q <= 4'b0000;
      end
      else if (reset) begin
         if (!PE) begin
            // parallel load
            Q <= P;       
         end
         else if (PE & CET & CEB) begin
            Q <= Q + 1'b1;        
         end           
      end
   end 
  
 The 74x590 is one of my more often used chips in the drawer because it increases the counter size to 8 bits and adds the ability to tristate the outputs.  It also has a separate register clock so the internal counter can be changed without making those changes visible on the outputs until a separate clock signal occurs.


   // tristate the outputs
   assign Q = (G) ? 8'bzzzzzzzz : Qreg;

   // RCO is the saturation flag
   assign RCO = Qreg & 8'b11111111;
     
   // copy the internal counts to the registers on RCK
   always @ (posedge RCK) begin
      Qreg <= Qint;     
   end
  
   // reset applies to the internal counters.
   // increment on CCK
   always @ (posedge CCK or negedge reset) begin
      if (!reset) begin
         Qint <= 8'b00000000;
      end
      else if (reset & CCKEN) begin
         Qint <= Qint + 1;
      end 
   end  
While there are lots of variants for the counter chips,  I will mention here the 74x569 which adds the ability to count both up or down.  like the other variants there is a bcd version (74x568) but it's easier to understand the binary counters.  It also includes both sync. and async. resets... yay...  This is one of my favorites...


   // assign for terminal count output
   assign TC = Qreg & 4'b1111; // state 1111

   // tristates for output enable (OE)
   assign Q = (OE) ? 4'bz : Qreg;  

   // CC clock propagation
   assign CC = (SR & PE & !CET & !CEP & !TC) ? clock : 1'b1;

   // up/down counter - sync and async reset, 2 enables, direction
   always @ (posedge clock or negedge reset) begin
      if (!reset) begin
         // async. reset
         Qreg <= 4'b0000;  
      end
      else if (reset) begin
         if (!SR) begin
            // sync. reset
            Qreg <= 4'b0000;           
         end
         else if (SR & !PE) begin
            // parallel load
            Qreg <= D;     
         end
         else if (SR & !CEP & !CET & UD) begin
            Qreg <= Qreg + 1;      
         end
         else if (SR & !CEP & !CET & !UD) begin
            Qreg <= Qreg - 1;      
         end
      end
   end  
I'd also like to mention the 74x4520... this chip contains two 4 bits counters and while it doesn't have parallel load or up/down directional control or easy chaining signals it has the ability to use either the positive edge of the clock or the negative edge of the enable line to increment the counter. 

The hdl is easy in verilog...

assign tripA = !(!clkA & enA);
   assign tripB = !(!clkB & enB);
        
   always @ (posedge tripA or posedge resetA) begin
      if (resetA) begin
         // async. reset
         QA <= 4'b0000;  
      end
      else if (!resetA) begin
         QA <= QA + 1;  
      end
   end
  
   always @ (posedge tripB or posedge resetB) begin
      if (resetB) begin
         // async. reset
         QB <= 4'b0000;  
      end
      else if (!resetB) begin
         QB <= QB + 1;  
      end
   end  
  
Just to give you an idea of how nice it is to have verilog do the connections between the registers when they are grouped (so they behave like a multidigit number), let's look at the wrong way to code a 74x568 (just like the 74x569 above but in bcd instead of binary).  (This isn't really the "wrong way" but it's an inefficient way - logically treating the 1:2 and 1:5 counters separately and within the 1:5 treating the registers separately and having explicit states for the up as well as the down movements)... perhaps it would be better to say the "ugly and difficult to maintain and understand" way rather than the "wrong way"...

 // assign for terminal count output
   assign RCO = Q0 & !Q1 & !Q2 & Q3; // state 1001

   // tristates for output enable (OE)
   assign Q = (OE) ? 4'bz : {Q3, Q2, Q1, Q0};  


   // Just for fun I'm doing every state with up/down as an input
   // rather than the clean way... I'll use the clean way with the
   // binary counter version... I'm doing this to show how increasing the
   // abilities of a module can become unmanageable... You should quickly
   // realize that setting up a simple state machine with external control logic
   // would be more efficient...
   always @ (posedge clock or negedge reset) begin
      if (!reset) begin
         Q0 = 1'b0;
         Q1 = 1'b0;
         Q2 = 1'b0;
         Q3 = 1'b0;     
      end
      else if (reset) begin
         if (!SR) begin
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end
         else if (!load) begin
            // parallel load
            Q0 = A;
            Q1 = B;
            Q2 = C;
            Q3 = D;       
         end
         else if (load & !ENP & !ENT & !Q0 & !Q1 & !Q2 & !Q3 & UD) begin
            // state 0000
            Q0 = 1'b1;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end    
         else if (load & !ENP & !ENT &  Q0 & !Q1 & !Q2 & !Q3 & UD) begin
            // state 1000
            Q0 = 1'b0;
            Q1 = 1'b1;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end            
         else if (load & !ENP & !ENT & !Q0 &  Q1 & !Q2 & !Q3 & UD) begin
            // state 0100
            Q0 = 1'b1;
            Q1 = 1'b1;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &   Q0 &  Q1 & !Q2 & !Q3 & UD) begin
            // state 1100
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &  !Q0 &  !Q1 &  Q2 & !Q3 & UD) begin
            // state 0010
            Q0 = 1'b1;
            Q1 = 1'b0;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &   Q0 &  !Q1 &  Q2 & !Q3 & UD) begin
            // state 1010
            Q0 = 1'b0;
            Q1 = 1'b1;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &  !Q0 &  Q1 &  Q2 & !Q3 & UD) begin
            // state 0110
            Q0 = 1'b1;
            Q1 = 1'b1;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &   Q0 &   Q1 &  Q2 & !Q3 & UD) begin
            // state 1110
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b1;             
         end                    
         else if (load & !ENP & !ENT &  !Q0 &  !Q1 & !Q2 &  Q3 & UD) begin
            // state 1000
            Q0 = 1'b1;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b1;             
         end            
         else if (load & !ENP & !ENT &   Q0 &  !Q1 & !Q2 &  Q3 & UD) begin
            // state 1001
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end
         else if (load & !ENP & !ENT & !Q0 & !Q1 & !Q2 & !Q3 & !UD) begin
            // state 1001
            Q0 = 1'b1;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b1;             
         end    
         else if (load & !ENP & !ENT &  Q0 & !Q1 & !Q2 & !Q3 & !UD) begin
            // state 0000
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end            
         else if (load & !ENP & !ENT & !Q0 &  Q1 & !Q2 & !Q3 & !UD) begin
            // state 1000
            Q0 = 1'b1;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &   Q0 &  Q1 & !Q2 & !Q3 & !UD) begin
            // state 0100
            Q0 = 1'b0;
            Q1 = 1'b1;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &  !Q0 &  !Q1 &  Q2 & !Q3 & !UD) begin
            // state 1100
            Q0 = 1'b1;
            Q1 = 1'b1;
            Q2 = 1'b0;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &   Q0 &  !Q1 &  Q2 & !Q3 & !UD) begin
            // state 0010
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &  !Q0 &  Q1 &  Q2 & !Q3 & !UD) begin
            // state 1010
            Q0 = 1'b1;
            Q1 = 1'b0;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &   Q0 &   Q1 &  Q2 & !Q3 & !UD) begin
            // state 0110
            Q0 = 1'b0;
            Q1 = 1'b1;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end                    
         else if (load & !ENP & !ENT &  !Q0 &  !Q1 & !Q2 &  Q3 & !UD) begin
            // state 1110
            Q0 = 1'b1;
            Q1 = 1'b1;
            Q2 = 1'b1;
            Q3 = 1'b0;             
         end            
         else if (load & !ENP & !ENT &   Q0 &  !Q1 & !Q2 &  Q3 & !UD) begin
            // state 0001
            Q0 = 1'b0;
            Q1 = 1'b0;
            Q2 = 1'b0;
            Q3 = 1'b1;             
         end
      end
   end  
If you look back at the 74x569, imagine that you just made a 1 bit and a 3 bit counter (trapped at 4) instead of the 4 bit binary counter.  It would take very little additional logic to connect overflow/underflow (add to the 1 bit pass overflow to the clock for the 3 bit one to add - and - subtract from the 3 bit and pass the underflow to the 1 bit to subtract) and you would have a cute little easy to understand and maintain hdl... In general if you have lots of things involved in state changes withing a block, or lots of states withing a block... there is an easier way to design it.  The important thing to remember is that there isn't a right or wrong way, there are always tradeoffs in size and speed and maintainability as well as style.

Counters are extremely useful, but I find that many times the design of the rest of the circuit gets adjusted to the counter that you want to use... while there are a number of variants with loading and triggering and signal passing capabilities, there isn't a great way to build a really universal module (which is why there are so many variations produced).  If you are designing the hardware it's relatively easy to build the exact counter you need - but when you have to start adding more and more control logic before the counter you have available, it's easy to have timing problems at higher speeds.

Tuesday, March 22, 2011

Remembering classic logic... (part 8)

Recently I pulled out my boxes of 7400 series logic (working on a few old school projects that were idled for a bit).  There's something special about these chips...

So, on a whim I thought that I'd bang out verilog modules for the chips I have on hand.  I'm putting the files for these modules in the tanukifu project here.


There are many times when exactly one of a set of lines needs to be set differently (high or low) then the other members in the group.  Demuxes (De-multiplexers) do this by having more outputs than inputs (always a power of 2 since we are unfolding a binary number - the inputs are often considered address lines).   Just about any time you have multiple chips on a bus and need to communicated with them individually you need a mus (unless you want to put a lot of overhead on the bus and additional logic in each chip).


It's not unusual for multiple demuxes to be chained together and for complex logic to determine when output should be expressed so many times demuxes will have multiple enables in various groups of active high/active low lines.  While the 74x138 is less complex, the 74x137 is one of the more popular chips in my box.  The hdl is easy in verilog...




   // If GL is low and G2 is high or G1 is low then the outputs are high
   assign Y1 = (!GL & (G2 | !G1)) ? 1'b1 : ~Yreg1;
   assign Y2 = (!GL & (G2 | !G1)) ? 1'b1 : ~Yreg2;
   assign Y3 = (!GL & (G2 | !G1)) ? 1'b1 : ~Yreg3;
   assign Y4 = (!GL & (G2 | !G1)) ? 1'b1 : ~Yreg4;
   assign Y5 = (!GL & (G2 | !G1)) ? 1'b1 : ~Yreg5;
   assign Y6 = (!GL & (G2 | !G1)) ? 1'b1 : ~Yreg6;
   assign Y7 = (!GL & (G2 | !G1)) ? 1'b1 : ~Yreg7;
   assign Y8 = (!GL & (G2 | !G1)) ? 1'b1 : ~Yreg8;
     
   // If GL is low and G2 is low and G1 is high then the selects act on Y1 to Y8.
   // If GL is high and G2 is low and G1 is high then the last state set is still presented
   // Direct binary counting...
   always @ (*) begin
      if (!GL & G1 & !G2) begin
     Yreg1 <= (!Asel & !Bsel & !Csel); // 000
     Yreg2 <= (!Asel & !Bsel &  Csel); // 001
     Yreg3 <= (!Asel &  Bsel & !Csel); // 010
     Yreg4 <= (!Asel &  Bsel &  Csel); // 011
     Yreg5 <= ( Asel & !Bsel & !Csel); // 100
     Yreg6 <= ( Asel & !Bsel &  Csel); // 101
     Yreg7 <= ( Asel &  Bsel & !Csel); // 110
     Yreg8 <= ( Asel &  Bsel &  Csel); // 111   
      end
   end
The most popular demux for me is the 74x538.  Here we have not only multiple enables, but also multiples output enables witht he ability to tristate the outputs - so we can have a bus that controls a bus in effect.  It also have the magical P input which allows us to set the not enabled state to either high or low on demand.  The simpler 74x238 and 74x138 chips are useful as well but aren't as adaptable...

Still easy to do in verilog...
   // Tristate unless both enables are low
   assign Y1 = (!Oen1 & !Oen2) ? Yint1 : 1'bz;
   assign Y2 = (!Oen1 & !Oen2) ? Yint2 : 1'bz;
   assign Y3 = (!Oen1 & !Oen2) ? Yint3 : 1'bz;
   assign Y4 = (!Oen1 & !Oen2) ? Yint4 : 1'bz;
   assign Y5 = (!Oen1 & !Oen2) ? Yint5 : 1'bz;
   assign Y6 = (!Oen1 & !Oen2) ? Yint6 : 1'bz;
   assign Y7 = (!Oen1 & !Oen2) ? Yint7 : 1'bz;
   assign Y8 = (!Oen1 & !Oen2) ? Yint8 : 1'bz;
  
  
   // set the state if E1 and E2 are low and E3 and E4 are high otherwise all outputs are P
   assign Yint1 = (!E1 & !E2 & E3 & E4) ?  (!Asel & !Bsel & !Csel) : P;
   assign Yint2 = (!E1 & !E2 & E3 & E4) ?  (!Asel & !Bsel &  Csel) : P;
   assign Yint3 = (!E1 & !E2 & E3 & E4) ?  (!Asel &  Bsel & !Csel) : P;
   assign Yint4 = (!E1 & !E2 & E3 & E4) ?  (!Asel &  Bsel &  Csel) : P;
   assign Yint5 = (!E1 & !E2 & E3 & E4) ?  ( Asel & !Bsel & !Csel) : P;
   assign Yint6 = (!E1 & !E2 & E3 & E4) ?  ( Asel & !Bsel &  Csel) : P;
   assign Yint7 = (!E1 & !E2 & E3 & E4) ?  ( Asel &  Bsel & !Csel) : P;
   assign Yint8 = (!E1 & !E2 & E3 & E4) ?  ( Asel &  Bsel &  Csel) : P;

Another useful variant is the 74x157 which is the classic quad 2 to 1 mux.   You can think of it as selecting between two 4 bit groups, two 3 bit groups plus a 1 bit group, two 2 bit groups, or four 1 bit groups... by chaining them together this can be 2 of any number of groups.
The hdl is easy in verilog...




   // bind outputs to selected input or gnd based on strobe
   assign Y1 = strobe ? Ysel1 : 1'b0;
   assign Y2 = strobe ? Ysel2 : 1'b0;
   assign Y3 = strobe ? Ysel3 : 1'b0;
   assign Y4 = strobe ? Ysel4 : 1'b0;

   // connect output selection based on select state
   assign Ysel1 = select ? B1 : A1;
   assign Ysel2 = select ? B2 : A2;
   assign Ysel3 = select ? B3 : A3;
   assign Ysel4 = select ? B4 : A4;

As with muxes you can do more complex math by using feedback designs and when linked to memory/registers some of the basic elements of processors can be built.

Next time I think we'll do counters...

Monday, March 21, 2011

Remembering classic logic... (part 7)

Recently I pulled out my boxes of 7400 series logic (working on a few old school projects that were idled for a bit).  There's something special about these chips...

So, on a whim I thought that I'd bang out verilog modules for the chips I have on hand.  I'm putting the files for these modules in the tanukifu project here.


There are times when one has a number of separate lines (usually connected to similar outputs of different chips) and a single input needs to be connected to exactly one of these outputs... A mux (multiplexer) uses a small number of address lines to determine which of the inputs should be connected to the outputs.  The 74x151 allows 3 address lines to select from 1 of 8 inputs and to present that line as well as it's compliment as outputs.


The hdl is easy here...


// if the strobe is low then set the outputs to the input based on
   // the select lines, otherwise Aout is low and Ainv is high.
   assign Aout = (strobe) ? 1'b0 :  Aint;
   assign Ainv = ~Aout;

   assign select = {Asel, Bsel, Csel};
  
   always @ (*) begin
      case (select)
        (3'b000) : Aint = A1;
        (3'b001) : Aint = A2;
        (3'b010) : Aint = A3;
        (3'b011) : Aint = A4;
        (3'b100) : Aint = A5;
        (3'b101) : Aint = A6;
        (3'b110) : Aint = A7;
        (3'b111) : Aint = A8;
      endcase
   end
The 74x451 effectively combines two 8 to 1 muxes and makes the outputs tristates. 

// if the strobe is low then set the outputs to the input based on
   // the select lines, otherwise Aout is low.
   assign Aout = (strobe) ? 1'b0 :  Aint;
   assign Bout = (strobe) ? 1'b0 :  Bint;

   assign select = {Asel, Bsel, Csel};
  
   always @ (*) begin
      case (select)
        (3'b000) : Aint = A1;
        (3'b001) : Aint = A2;
        (3'b010) : Aint = A3;
        (3'b011) : Aint = A4;
        (3'b100) : Aint = A5;
        (3'b101) : Aint = A6;
        (3'b110) : Aint = A7;
        (3'b111) : Aint = A8;
      endcase
   end // always @ (*)
  
   always @ (*) begin
      case (select)
        (3'b000) : Bint = B1;
        (3'b001) : Bint = B2;
        (3'b010) : Bint = B3;
        (3'b011) : Bint = B4;
        (3'b100) : Bint = B5;
        (3'b101) : Bint = B6;
        (3'b110) : Bint = B7;
        (3'b111) : Bint = B8;
      endcase
   end
The 74x251 is a single 8 to 1 mux with tristates but includes complimentary outputs.  This is particularly useful when muxes are used to build math functions.

// if the strobe is low then set the outputs to the input based on
   // the select lines, otherwise Aout is low and Ainv is high.
   assign Aout = (enable) ? 1'bz :  Aint;
   assign Ainv = (enable) ? 1'bz : ~Aint;

   assign select = {Asel, Bsel, Csel};
  
   always @ (*) begin
      case (select)
        (3'b000) : Aint = A1;
        (3'b001) : Aint = A2;
        (3'b010) : Aint = A3;
        (3'b011) : Aint = A4;
        (3'b100) : Aint = A5;
        (3'b101) : Aint = A6;
        (3'b110) : Aint = A7;
        (3'b111) : Aint = A8;
      endcase
   end
The last one I like to use is the 74x353 which contains two separate 4 to 1 muxes with the same address lines (but here the outputs are inverted).  It's a lot more common for me to need 2 smaller muxes in a design than 1 larger one.
  assign Aout = (enable) ? 1'bz : ~Aint;
   assign Bout = (enable) ? 1'bz : ~Bint;

   assign select = {Asel, Bsel};
  
   always @ (*) begin
      case (select)
        (2'b00) : Aint = A1;
        (2'b01) : Aint = A2;
        (2'b10) : Aint = A3;
        (2'b11) : Aint = A4;
      endcase
   end
  
   always @ (*) begin
      case (select)
        (2'b00) : Bint = B1;
        (2'b01) : Bint = B2;
        (2'b10) : Bint = B3;
        (2'b11) : Bint = B4;
      endcase
   end        
Most of the time I used muxes for scanning low speed (not time sensitive) inputs for a state change, or selecting which of several similar devices to pay attention to (if I can't connect them to a common bus).  Sometimes I used them in feedback configurations to make more complicated logic.  There are some very interesting things they can do when mixed with ram and a few other logic chips that form the basics of a processor.   One can also use these chips to connect active pipes (higher speed serial connections) from multiple devices to drive an even faster connection efficiently.

I tended to find demuxes (de-multiplexers) more useful (those we cove next time).