MyHDL Primer -FIFO (First In First Out)
- By Abhishek Bajpai
- Mon 14 November 2016
image: assets/images/MyHDL.jpg
MyHDL is a python based HDL language developed by Jan Decaluwe. Here we will talk about how to write code in MyHDL.
For purpose of simplicity we will try to create FIFO and its test bench.
What is FIFO?
FIFO is an acronym for first in, first out, a method for organizing and manipulating a data buffer, where the oldest (first) entry, or ‘head’ of the queue, is processed first. It is analogous to processing a queue with first-come, first-served (FCFS) behaviour: where the people leave the queue in the order in which they arrive. curtsy wikipedia
So lets talk about interface signals for a FIFO module.
Half full FIFO model
- clk : input signal for clock
- inbusy : input bus is busy
- we : input bus write enable
- din : input bus data input
- outbusy: output bus is busy
- rd : output bus read enable
- dout : output bus data output
- rdout : output bus data valid
- rst : input reset
These are the obvious signals one should have in the FIFO module. “inbusy” and “outbusy” signals are important in order to signal master that the buffer overflow or underflow event has occured.
from myhdl import block
DATA=8 #Size of Data bus
WORD=8 #Size of Data bus
ADDR=8 #Size of address bus
LOWER=2 #lower limit for FIFO read
UPPER=(2**8)-8 #upper limit for FIFO write
OFFSET=(2**8)-8 #upper limit for FIFO write
@block
def fifo(clk, inbusy, we, din, outbusy, rd, dout, rdout, rst,
DATA=DATA, ADDR=ADDR, LOWER=LOWER, UPPER=UPPER, OFFSET=OFFSET):
return instances()
Here @block represents module in HDL. And instances() return all the instences defined in the module. Presently no instances are defined.
Signals (wire/registers)
Lets define some memory for fifo. Here “DATA” is the bus width for the mem register array and “2**ADDR” is the count of total such registers.
mem = [Signal(modbv(0)[DATA:]) for i in range(2**ADDR)]
Lets define some signals. “in_addr” and “out_addr” are two pointer signals which points to the present memory address for the corresponding input and output buses.
in_addr, out_addr, diff = [Signal(modbv(0)[ADDR+1:]) for i in range(3)]
noread.next = Signal(bool(0))
Now lets write some instances. “@always_seq(clk.posedge, reset = rst)” emplies a sequential instance where event trigers on positive clock edge. Further all registers gets reset to their initial values on reset event.
Write Logic instances
@always_seq(clk.posedge, reset = rst)
def write_port():
if we:
in_addr.next = in_addr + modbv(1)[ADDR+1:]
if (diff >= UPPER): # Logic for buff overflow
inbusy.next = bool(1)
else:
inbusy.next = bool(0)
@always(inclk.posedge)
def write_mem_logic():
if we:
mem[in_addr[ADDR:]].next = din
Read Logic instances
@always_seq(clk.posedge, reset = rst) # @always(outclk.posedge)
def read_port():
dout.next = mem[out_addr[ADDR:]]
if rd and not noread:
out_addr.next = out_addr + modbv(1)[ADDR+1:]
rdout.next = rd
else:
rdout.next = bool(0)
In order to avoid buffer overflow and underflow events we have to take care of the gap between “in_addr” and “out_addr”
@always_comb
def diffLogic():
diff.next = in_addr - out_addr
@always_comb
def outbusyLogic():
if (diff <= LOWER): # Logic for buff underflow
outbusy.next = bool(1)
noread.next = bool(1)
else:
outbusy.next = bool(0)
noread.next = bool(0)
Let us see the complete code
#fifo.py
from myhdl import block
DATA=8 #Size of Data bus
WORD=8 #Size of Data bus
ADDR=8 #Size of address bus
LOWER=2 #lower limit for FIFO read
UPPER=(2**8)-8 #upper limit for FIFO write
OFFSET=(2**8)-8 #upper limit for FIFO write
@block
def cfifo(clk, inbusy, we, din, outbusy, rd, dout, rdout, rst,
DATA=DATA, ADDR=ADDR, LOWER=LOWER, UPPER=UPPER, OFFSET=OFFSET):
in_addr, out_addr, diff = [Signal(modbv(0)[ADDR+1:]) for i in range(3)]
mem = [Signal(modbv(0)[DATA:]) for i in range(2**ADDR)]
@always_seq(clk.posedge, reset = rst)
def write_port():
if we:
in_addr.next = in_addr + modbv(1)[ADDR+1:]
if (diff >= UPPER):
inbusy.next = bool(1)
else:
inbusy.next = bool(0)
@always(inclk.posedge)
def write_mem_logic():
if we:
mem[in_addr[ADDR:]].next = din
@always_seq(clk.posedge, reset = rst) # @always(outclk.posedge)
def read_port():
dout.next = mem[out_addr[ADDR:]]
if rd and not noread:
out_addr.next = out_addr + modbv(1)[ADDR+1:]
rdout.next = rd
else:
rdout.next = bool(0)
@always_comb
def diffLogic():
diff.next = in_addr - out_addr
@always_comb
def outbusyLogic():
#level.next = diff
if (diff <= LOWER):
outbusy.next = bool(1)
noread.next = bool(1)
else:
outbusy.next = bool(0)
noread.next = bool(0)
return instances()
Test Bench
In order to write a test bench we will use a puthon’s unit test framework. In order to use this frame work we will write a testcase class.
At first lets write a skelton class “TestFifo” which actually does noting.
#test_fifo.py
from unittest import TestCase
import unittest
class TestFifo(TestCase):
def testFifoBuffer(self):
""" Check that fifo can be write and read """
@block
def test_fifo():
return instances()
tb = test_cfifo()
tb.config_sim(trace=True)
tb.run_sim(duration=1500)
tb.quit_sim()
On running the test output is as follows.
abhishek@abhishek:~/2016/MyHDL_tutorial$ python -m test_fifo
<class 'myhdl.StopSimulation'>: No more events
.
----------------------------------------------------------------------
Ran 1 test in 0.017s
OK
Now lets populate the test bench. “createbuff” creates a test buffer that we will write to the fifo later.
def testBuffer(self):
""" Check that buffer can be write and read """
ADDR = 4
DATA = 16
OFFSET = 8
LOWER = 4
UPPER =2**ADDR-4
WATCHDOG = 3000
def createbuff(DATA, ADDR):
return [Signal(intbv(randrange(2**DATA))) for i in range(2**4)]
Now here again “@block” we are defining a test bench module to test fifo. Various signals have been defined. “fifo_inst” that is device under test, also defined here.
clk = Signal(bool(0))
rst = ResetSignal(0, active=1, async=False)
we, rd, inbusy, outbusy, hfull, rdout = [Signal(bool(0)) for i in range(6)]
din, dout = [Signal(intbv(0)[DATA:]) for i in range(2)]
fifo_inst = fifo(clk, inbusy, we, din,
outbusy, rd, dout, rdout, rst,
ADDR=ADDR, DATA=DATA, OFFSET=OFFSET, LOWER=LOWER, UPPER=UPPER)
“fifo_inst.convert” converts fifo MyHDL model to the verilog.
fifo_inst.convert(hdl='Verilog', name='fifo_' + str(ADDR)+'_'+ str(DATA)+'_'
+ str(OFFSET)+'_'+ str(LOWER)+'_'+ str(UPPER))
Lets generate “clk” and “rst” logic signals.
@always(delay(8))
def clkgen():
clk.next = not clk
rambuff = createbuff(DATA, ADDR)
@instance
def stimulus():
yield delay(20)
rst.next = 1
yield delay(20)
rst.next = 0
yield delay(20)
Lets logic for writing to and reading from fifo.
@instance
def write():
we.next = Signal(bool(0))
yield delay(50)
for data in rambuff:
yield clk.posedge
while inbusy:
we.next = 0
yield clk.posedge
we.next = 1
din.next = data
yield clk.posedge
we.next = 0
#din.next = 0
@instance
def read():
watchdog = 30
watchctr = 0
yield delay(250)
j = 0
while 1:
yield clk.posedge
if watchctr == watchdog:
watchctr = 0
break
if not outbusy:
rd.next = Signal(bool(1))
else:
rd.next = Signal(bool(0))
watchctr = watchctr + 1
#print watchctr
if (rdout):
print hex(dout), hex(rambuff[j])
#self.assertEqual(dout, rambuff[j])
j = j + 1
yield clk.posedge
rd.next = Signal(bool(0))
Here is the complete code for test bench.
from unittest import TestCase
import unittest
from myhdl import block, instances, Signal, modbv, ResetSignal, intbv, always, delay, instance
from fifo import fifo
from random import randrange
class TestFifo(TestCase):
def testFifoBuffer(self):
""" Check that fifo can be write and read """
ADDR = 4
DATA = 16
OFFSET = 8
LOWER = 4
UPPER =2**ADDR-4
WATCHDOG = 3000
def createbuff(DATA, ADDR):
return [Signal(intbv(randrange(2**DATA))) for i in range(2**4)]
@block
def tb_fifo():
clk = Signal(bool(0))
rst = ResetSignal(0, active=1, async=False)
we, rd, inbusy, outbusy, hfull, rdout = [Signal(bool(0)) for i in range(6)]
din, dout = [Signal(intbv(0)[DATA:]) for i in range(2)]
fifo_inst = fifo(clk, inbusy, we, din,
outbusy, rd, dout, rdout, rst,
ADDR=ADDR, DATA=DATA, OFFSET=OFFSET, LOWER=LOWER, UPPER=UPPER)
fifo_inst.convert(hdl='Verilog', name='fifo_' + str(ADDR)+'_'+ str(DATA)+'_'
+ str(OFFSET)+'_'+ str(LOWER)+'_'+ str(UPPER))
@always(delay(8))
def clkgen():
clk.next = not clk
rambuff = createbuff(DATA, ADDR)
@instance
def stimulus():
yield delay(20)
rst.next = 1
yield delay(20)
rst.next = 0
yield delay(20)
@instance
def write():
we.next = Signal(bool(0))
yield delay(50)
for data in rambuff:
yield clk.posedge
while inbusy:
we.next = 0
yield clk.posedge
we.next = 1
din.next = data
yield clk.posedge
we.next = 0
#din.next = 0
@instance
def read():
watchdog = 30
watchctr = 0
yield delay(250)
j = 0
while 1:
yield clk.posedge
if watchctr == watchdog:
watchctr = 0
break
if not outbusy:
rd.next = Signal(bool(1))
else:
rd.next = Signal(bool(0))
watchctr = watchctr + 1
#print watchctr
if (rdout):
print hex(dout), hex(rambuff[j])
#self.assertEqual(dout, rambuff[j])
j = j + 1
yield clk.posedge
rd.next = Signal(bool(0))
return instances()
tb = tb_fifo()
tb.config_sim(trace=True)
tb.run_sim(duration=1500)
tb.quit_sim()
if __name__ == '__main__':
unittest.main()
here is the output while execution of the test bench.
abhishek@abhishek:~/2016/MyHDL_tutorial$ python2 -m test_fifo
0xd71e 0xd71e
0xdfcb 0xdfcb
0xdb09 0xdb09
0xaa7a 0xaa7a
0x7166 0x7166
0xf851 0xf851
0x2fbf 0x2fbf
0x4cb1 0x4cb1
0x5f3d 0x5f3d
0x32f4 0x32f4
0x67ac 0x67ac
0x4273 0x4273
<class 'myhdl._SuspendSimulation'>: Simulated 1500 timesteps
.
----------------------------------------------------------------------
Ran 1 test in 0.064s
OK
It also generates signal traces that we can see by reading generated *.vcd file in gtkwave.
Finally lets see the converted design.
// File: fifo_4_16_8_4_12.v
// Generated by MyHDL 1.0dev
// Date: Sat Nov 19 08:42:16 2016
`timescale 1ns/10ps
module fifo_4_16_8_4_12 (
clk,
inbusy,
we,
din,
outbusy,
rd,
dout,
rdout,
rst
);
input clk;
output inbusy;
reg inbusy;
input we;
input [15:0] din;
output outbusy;
reg outbusy;
input rd;
output [15:0] dout;
reg [15:0] dout;
output rdout;
reg rdout;
input rst;
reg [4:0] out_addr;
reg [4:0] in_addr;
reg noread;
wire [4:0] diff;
reg [15:0] mem [0:16-1];
always @(posedge clk) begin: FIFO_4_16_8_4_12_WRITE_PORT
if (rst == 1) begin
inbusy <= 0;
in_addr <= 0;
end
else begin
if (we) begin
in_addr <= (in_addr + 5'h1);
end
if ((diff >= 12)) begin
inbusy <= (1 != 0);
end
else begin
inbusy <= (0 != 0);
end
end
end
always @(posedge clk) begin: FIFO_4_16_8_4_12_READ_PORT
if (rst == 1) begin
out_addr <= 0;
rdout <= 0;
dout <= 0;
end
else begin
dout <= mem[out_addr[4-1:0]];
if ((rd && (!noread))) begin
out_addr <= (out_addr + 5'h1);
rdout <= rd;
end
else begin
rdout <= (0 != 0);
end
end
end
always @(posedge clk) begin: FIFO_4_16_8_4_12_WRITE_MEM_LOGIC
if (we) begin
mem[in_addr[4-1:0]] <= din;
end
end
assign diff = (in_addr - out_addr);
always @(diff) begin: FIFO_4_16_8_4_12_OUTBUSYLOGIC
if ((diff <= 4)) begin
outbusy = (1 != 0);
noread = (1 != 0);
end
else begin
outbusy = (0 != 0);
noread = (0 != 0);
end
end
endmodule