├── libsv
├── __init__.py
├── math
│ ├── half_adder.sv
│ └── full_adder.sv
├── counters
│ └── binary_counter.sv
├── latches
│ └── sr_latch.sv
├── coders
│ ├── gray_encoder.sv
│ ├── gray_decoder.sv
│ ├── onehot_priority_encoder.sv
│ ├── priority_encoder.sv
│ ├── bcd_encoder.sv
│ └── bcd_decoder.sv
├── bit_ops
│ └── rotate.sv
├── muxes
│ └── onehot_mux.sv
├── synchronizers
│ └── synchronizer.sv
├── fifos
│ ├── sync_fifo.sv
│ └── skid_buffer.sv
└── arbiters
│ └── ring_arbiter.sv
├── docs
├── requirements.txt
├── source
│ ├── _static
│ │ ├── style.css
│ │ └── libsv_logo.svg
│ ├── fifos.rst
│ ├── muxes.rst
│ ├── latches.rst
│ ├── arbiters.rst
│ ├── math.rst
│ ├── bit_ops.rst
│ ├── counters.rst
│ ├── _templates
│ │ └── layout.html
│ ├── modules.rst
│ ├── coders.rst
│ ├── contact.rst
│ ├── license.rst
│ ├── useful_links.rst
│ ├── half_adder.rst
│ ├── full_adder.rst
│ ├── binary_counter.rst
│ ├── rotate.rst
│ ├── bcd_encoder.rst
│ ├── bcd_decoder.rst
│ ├── onehot_mux.rst
│ ├── sr_latch.rst
│ ├── onehot_priority_encoder.rst
│ ├── priority_encoder.rst
│ ├── conf.py
│ ├── skid_buffer.rst
│ ├── ring_arbiter.rst
│ ├── index.rst
│ ├── contributing.rst
│ ├── encoder_8b10b.rst
│ └── decoder_8b10b.rst
├── Makefile
└── make.bat
├── AUTHORS
├── .flake8
├── tests
├── conftest.py
├── coders
│ ├── test_bcd_decoder.py
│ ├── test_bcd_encoder.py
│ ├── test_onehot_priority_encoder.py
│ ├── test_priority_encoder.py
│ ├── test_gray_encoder.py
│ ├── test_gray_decoder.py
│ ├── test_encoder_8b10b.py
│ └── test_decoder_8b10b.py
├── math
│ ├── test_half_adder.py
│ └── test_full_adder.py
├── latches
│ └── test_sr_latch.py
├── muxes
│ └── test_onehot_mux.py
├── synchronizers
│ └── test_synchronizer.py
├── counters
│ └── test_binary_counter.py
├── utils.py
├── bit_ops
│ └── test_rotate.py
├── fifos
│ ├── test_skid_buffer.py
│ └── test_sync_fifo.py
└── arbiters
│ └── test_ring_arbiter.py
├── .gitignore
├── .github
└── workflows
│ ├── create-issue-branch.yml
│ └── ci.yml
├── .readthedocs.yaml
├── Dockerfile.dev
├── LICENSE
├── pyproject.toml
├── Dockerfile
├── README.rst
├── .verible-verilog-format.yaml
└── tools
└── precommit.py
/libsv/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | sphinx
2 | sphinx_rtd_theme
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Ben Sampson
2 |
3 | Contributors include::
4 |
5 |
--------------------------------------------------------------------------------
/docs/source/_static/style.css:
--------------------------------------------------------------------------------
1 | .wy-nav-content {
2 | max-width: none;
3 | }
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | max-doc-length = 88
4 | exclude = .git, dist
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 |
4 | sys.path.append(os.path.dirname(__file__))
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | dist/
3 | docs/build/
4 | .vscode/
5 | *.pyc
6 | .pytest_cache
7 | .python-version
--------------------------------------------------------------------------------
/docs/source/fifos.rst:
--------------------------------------------------------------------------------
1 | =====
2 | FIFOs
3 | =====
4 |
5 | .. toctree::
6 | :maxdepth: 1
7 |
8 | skid_buffer
--------------------------------------------------------------------------------
/docs/source/muxes.rst:
--------------------------------------------------------------------------------
1 | =====
2 | Muxes
3 | =====
4 |
5 | .. toctree::
6 | :maxdepth: 1
7 |
8 | onehot_mux
--------------------------------------------------------------------------------
/docs/source/latches.rst:
--------------------------------------------------------------------------------
1 | =======
2 | Latches
3 | =======
4 |
5 | .. toctree::
6 | :maxdepth: 1
7 |
8 | sr_latch
--------------------------------------------------------------------------------
/docs/source/arbiters.rst:
--------------------------------------------------------------------------------
1 | ========
2 | Arbiters
3 | ========
4 |
5 | .. toctree::
6 | :maxdepth: 1
7 |
8 | ring_arbiter
--------------------------------------------------------------------------------
/docs/source/math.rst:
--------------------------------------------------------------------------------
1 | ====
2 | Math
3 | ====
4 |
5 | .. toctree::
6 | :maxdepth: 1
7 |
8 | half_adder
9 | full_adder
--------------------------------------------------------------------------------
/docs/source/bit_ops.rst:
--------------------------------------------------------------------------------
1 | ==============
2 | Bit Operations
3 | ==============
4 |
5 | .. toctree::
6 | :maxdepth: 1
7 |
8 | rotate
--------------------------------------------------------------------------------
/docs/source/counters.rst:
--------------------------------------------------------------------------------
1 | ========
2 | Counters
3 | ========
4 |
5 | .. toctree::
6 | :maxdepth: 1
7 |
8 | binary_counter
9 | onehot_mux
--------------------------------------------------------------------------------
/docs/source/_templates/layout.html:
--------------------------------------------------------------------------------
1 | {% extends "!layout.html" %}
2 | {% block extrahead %}
3 |
4 | {% endblock %}
--------------------------------------------------------------------------------
/docs/source/modules.rst:
--------------------------------------------------------------------------------
1 | =======
2 | Modules
3 | =======
4 |
5 | .. toctree::
6 | :maxdepth: 2
7 |
8 | arbiters
9 | bit_ops
10 | coders
11 | counters
12 | fifos
13 | latches
14 | math
15 | muxes
--------------------------------------------------------------------------------
/docs/source/coders.rst:
--------------------------------------------------------------------------------
1 | ======
2 | Coders
3 | ======
4 |
5 | .. toctree::
6 | :maxdepth: 1
7 |
8 | bcd_decoder
9 | bcd_encoder
10 | decoder_8b10b
11 | encoder_8b10b
12 | onehot_priority_encoder
13 | priority_encoder
--------------------------------------------------------------------------------
/docs/source/contact.rst:
--------------------------------------------------------------------------------
1 | Contact
2 | =======
3 |
4 | * `LibSV issue tracker `_ to report bugs or request features.
5 | * `LibSV discussions `_ at GitHub for general questions.
--------------------------------------------------------------------------------
/docs/source/license.rst:
--------------------------------------------------------------------------------
1 | License
2 | =======
3 |
4 | Distributed under the terms of the `MIT `_ license, LibSV is free
5 | and open source software.
6 |
7 | .. literalinclude:: ../../LICENSE
8 | :language: text
9 |
--------------------------------------------------------------------------------
/docs/source/useful_links.rst:
--------------------------------------------------------------------------------
1 | Useful Links
2 | ============
3 |
4 | * `LibSV PyPI page `_
5 | * `LibSV GitHub page `_
6 | * `LibSV issue tracker `_
7 | * `LibSV discussions `_
8 | * :doc:`contributing` guide
--------------------------------------------------------------------------------
/libsv/math/half_adder.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_MATH_HALF_ADDER
2 | `define LIBSV_MATH_HALF_ADDER
3 |
4 | module half_adder (
5 | input logic i_a,
6 | input logic i_b,
7 | output logic o_sum,
8 | output logic o_carry
9 | );
10 |
11 | assign o_sum = i_a ^ i_b;
12 | assign o_carry = i_a & i_b;
13 |
14 | endmodule
15 |
16 | `endif /* LIBSV_MATH_HALF_ADDER */
17 |
--------------------------------------------------------------------------------
/libsv/math/full_adder.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_MATH_FULL_ADDER
2 | `define LIBSV_MATH_FULL_ADDER
3 |
4 | module full_adder (
5 | input logic i_a,
6 | input logic i_b,
7 | input logic i_carry,
8 | output logic o_sum,
9 | output logic o_carry
10 | );
11 |
12 | assign o_sum = i_a ^ i_b ^ i_carry;
13 | assign o_carry = ((i_a | i_b) & i_carry) | (i_a & i_b);
14 |
15 | endmodule
16 |
17 | `endif /* LIBSV_MATH_FULL_ADDER */
18 |
--------------------------------------------------------------------------------
/.github/workflows/create-issue-branch.yml:
--------------------------------------------------------------------------------
1 | on:
2 | issues:
3 | types: [assigned]
4 | issue_comment:
5 | types: [created]
6 | pull_request:
7 | types: [closed]
8 |
9 | jobs:
10 | create_issue_branch_job:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Create Issue Branch
14 | uses: robvanderleek/create-issue-branch@main
15 | env:
16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Set OS and Python version
9 | build:
10 | os: ubuntu-22.04
11 | tools:
12 | python: "3.7"
13 |
14 | # Build documentation in the docs/source directory with Sphinx
15 | sphinx:
16 | builder: html
17 | configuration: docs/source/conf.py
18 |
19 | python:
20 | install:
21 | - requirements: docs/requirements.txt
--------------------------------------------------------------------------------
/libsv/counters/binary_counter.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_COUNTERS_BINARY_COUNTER
2 | `define LIBSV_COUNTERS_BINARY_COUNTER
3 |
4 | module binary_counter #(
5 | parameter integer N /* verilator public_flat_rd */ = 8
6 | ) (
7 | input logic clk,
8 | input logic aresetn,
9 | output logic [N-1:0] q
10 | );
11 |
12 | always_ff @(posedge clk or negedge aresetn)
13 | if (!aresetn) q <= 0;
14 | else q <= q + 1;
15 |
16 | endmodule
17 |
18 | `endif /* LIBSV_COUNTERS_BINARY_COUNTER */
19 |
--------------------------------------------------------------------------------
/libsv/latches/sr_latch.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_LATCHES_SR_LATCH
2 | `define LIBSV_LATCHES_SR_LATCH
3 |
4 | module sr_latch (
5 | input logic s,
6 | input logic r,
7 | output logic /* verilator lint_off UNOPTFLAT */ q /* verilator lint_on UNOPTFLAT */,
8 | output logic q_n
9 | );
10 |
11 | assign q = ~(r | q_n);
12 | assign q_n = ~(s | q);
13 |
14 | endmodule
15 |
16 | `endif /* LIBSV_LATCHES_SR_LATCH */
17 |
--------------------------------------------------------------------------------
/tests/coders/test_bcd_decoder.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.triggers import Timer
3 | from utils import pytest_cocotb_run_test
4 |
5 |
6 | def test_bcd_decoder(pytestconfig):
7 | """Pytest fixture for BCD Decoder test"""
8 | pytest_cocotb_run_test(pytestconfig, __name__)
9 |
10 |
11 | @cocotb.test()
12 | async def cocotb_test_bcd_decoder(dut):
13 | """BCD Decoder test"""
14 |
15 | n = int(dut.N)
16 |
17 | for i in range(10**n):
18 | dut.i_bcd.value = int(str(int(i)), 16)
19 | await Timer(1)
20 | assert dut.o_bin == i
21 |
--------------------------------------------------------------------------------
/tests/coders/test_bcd_encoder.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.triggers import Timer
3 | from utils import pytest_cocotb_run_test
4 |
5 |
6 | def test_bcd_encoder(pytestconfig):
7 | """Pytest fixture for BCD Encoder test"""
8 | pytest_cocotb_run_test(pytestconfig, __name__)
9 |
10 |
11 | @cocotb.test()
12 | async def cocotb_test_bcd_encoder(dut):
13 | """BCD Encoder test"""
14 |
15 | n = int(dut.N)
16 |
17 | for i in range(2**n):
18 | dut.i_bin.value = i
19 | await Timer(1)
20 | assert dut.o_bcd == int(str(int(dut.i_bin)), 16)
21 |
--------------------------------------------------------------------------------
/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | ARG TAG=main
2 |
3 | FROM bensampson5/libsv:${TAG}
4 |
5 | ARG DEBIAN_FRONTEND=noninteractive
6 | RUN apt-get update \
7 | && apt-get install --no-install-recommends -y \
8 | sudo \
9 | && apt-get clean \
10 | && rm -rf /var/lib/apt/lists/*
11 |
12 | ARG UNAME=developer
13 | ARG UID=1000
14 | ARG GID=1000
15 | RUN groupadd -g $GID -o $UNAME
16 | RUN useradd -m -u $UID -g $GID -o -s /bin/bash $UNAME \
17 | && adduser $UNAME sudo \
18 | && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
19 | USER $UNAME
20 |
21 | WORKDIR /code
22 | CMD /bin/bash
--------------------------------------------------------------------------------
/docs/source/half_adder.rst:
--------------------------------------------------------------------------------
1 | ==========
2 | Half Adder
3 | ==========
4 |
5 | A half adder. Takes two inputs, ``i_a`` and ``i_b``, and adds them together to generate a sum output, ``o_sum``,
6 | and a carry bit output, ``o_carry``.
7 |
8 | Parameters
9 | ----------
10 | - None
11 |
12 | Ports
13 | -----
14 | - ``i_a`` input a
15 | - ``i_b`` input b
16 | - ``o_sum`` output sum
17 | - ``o_carry`` output carry
18 |
19 | Source Code
20 | -----------
21 | .. literalinclude:: ../../libsv/math/half_adder.sv
22 | :language: systemverilog
23 | :linenos:
24 | :caption: half_adder.sv
25 |
--------------------------------------------------------------------------------
/libsv/coders/gray_encoder.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_CODERS_GRAY_ENCODER
2 | `define LIBSV_CODERS_GRAY_ENCODER
3 |
4 | module gray_encoder #(
5 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 8
6 | ) (
7 | input logic [DATA_WIDTH-1:0] i_bin,
8 | output logic [DATA_WIDTH-1:0] o_gray
9 | );
10 |
11 | always_comb begin
12 | o_gray = '0;
13 | for (int i = 0; i < DATA_WIDTH - 1; ++i) begin
14 | o_gray[i] = i_bin[i] ^ i_bin[i+1];
15 | end
16 | o_gray[DATA_WIDTH-1] = i_bin[DATA_WIDTH-1];
17 | end
18 |
19 | endmodule
20 |
21 | `endif /* LIBSV_CODERS_GRAY_ENCODER */
22 |
--------------------------------------------------------------------------------
/docs/source/full_adder.rst:
--------------------------------------------------------------------------------
1 | ==========
2 | Full Adder
3 | ==========
4 |
5 | A full adder. Takes three inputs, ``i_a``, ``i_b``, and ``i_carry``, and adds them together to generate a sum output, ``o_sum``,
6 | and a carry bit output, ``o_carry``.
7 |
8 | Parameters
9 | ----------
10 | - None
11 |
12 | Ports
13 | -----
14 | - ``i_a`` input a
15 | - ``i_b`` input b
16 | - ``i_carry`` input carry
17 | - ``o_sum`` output sum
18 | - ``o_carry`` output carry
19 |
20 | Source Code
21 | -----------
22 | .. literalinclude:: ../../libsv/math/full_adder.sv
23 | :language: systemverilog
24 | :linenos:
25 | :caption: full_adder.sv
26 |
--------------------------------------------------------------------------------
/libsv/coders/gray_decoder.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_CODERS_GRAY_DECODER
2 | `define LIBSV_CODERS_GRAY_DECODER
3 |
4 | module gray_decoder #(
5 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 8
6 | ) (
7 | input logic [DATA_WIDTH-1:0] i_gray,
8 | output logic [DATA_WIDTH-1:0] o_bin
9 | );
10 |
11 | always_comb begin
12 | o_bin = '0;
13 | for (int i = 0; i < DATA_WIDTH - 1; ++i) begin
14 | for (int j = i; j < DATA_WIDTH; ++j) begin
15 | o_bin[i] ^= i_gray[j];
16 | end
17 | end
18 | o_bin[DATA_WIDTH-1] = i_gray[DATA_WIDTH-1];
19 | end
20 |
21 | endmodule
22 |
23 | `endif /* LIBSV_CODERS_GRAY_DECODER */
24 |
--------------------------------------------------------------------------------
/libsv/bit_ops/rotate.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_BIT_OPS_ROTATE
2 | `define LIBSV_BIT_OPS_ROTATE
3 |
4 | module rotate #(
5 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 8
6 | ) (
7 | input logic [ DATA_WIDTH-1:0] i_data,
8 | input logic [$clog2(DATA_WIDTH)-1:0] i_amt,
9 | output logic [ DATA_WIDTH-1:0] o_data
10 | );
11 |
12 | always_comb begin : rotate
13 | o_data = '0;
14 | for (int i = 0; i < DATA_WIDTH; ++i) begin
15 | int rotate_amt = (int'(i_amt) + i) % DATA_WIDTH;
16 | o_data[$bits(i_amt)'(rotate_amt)] = i_data[i];
17 | end
18 | end : rotate
19 |
20 | endmodule : rotate
21 |
22 | `endif /* LIBSV_BIT_OPS_ROTATE */
23 |
--------------------------------------------------------------------------------
/tests/math/test_half_adder.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.triggers import Timer
3 | from utils import pytest_cocotb_run_test
4 |
5 |
6 | def test_half_adder(pytestconfig):
7 | """Pytest fixture for Half Adder test"""
8 | pytest_cocotb_run_test(pytestconfig, __name__)
9 |
10 |
11 | @cocotb.test()
12 | async def cocotb_test_half_adder(dut):
13 | """Half adder test"""
14 |
15 | for i in range(2**2):
16 | i_a = i & 1
17 | i_b = (i >> 1) & 1
18 | o_sum = i_a ^ i_b
19 | o_carry = i_a & i_b
20 |
21 | dut.i_a.value = i_a
22 | dut.i_b.value = i_b
23 | await Timer(1)
24 | assert dut.o_sum == o_sum
25 | assert dut.o_carry == o_carry
26 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/tests/latches/test_sr_latch.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.triggers import Timer
3 | from utils import pytest_cocotb_run_test
4 |
5 |
6 | def test_sr_latch(pytestconfig):
7 | """Pytest fixture for SR Latch test"""
8 | pytest_cocotb_run_test(pytestconfig, __name__)
9 |
10 |
11 | @cocotb.test()
12 | async def cocotb_test_sr_latch(dut):
13 | """SR Latch test"""
14 |
15 | dut.s.value = 0
16 | dut.r.value = 0
17 | await Timer(1)
18 |
19 | dut.s.value = 1
20 | await Timer(1)
21 | assert dut.q == 1
22 | assert dut.q_n == 0
23 |
24 | dut.s.value = 0
25 | await Timer(1)
26 |
27 | dut.r.value = 1
28 | await Timer(1)
29 | assert dut.q == 0
30 | assert dut.q_n == 1
31 |
--------------------------------------------------------------------------------
/docs/source/binary_counter.rst:
--------------------------------------------------------------------------------
1 | ==============
2 | Binary Counter
3 | ==============
4 |
5 | This module implements a parameterized binary counter with an active-low asynchronous reset.
6 |
7 | With every clock rising, the counter's output ``q`` is incremented by 1. And with an
8 | assertion of the active-low reset, ``aresetn``, ``q`` is set to 0.
9 |
10 | Parameters
11 | ----------
12 | - ``N`` number of bits
13 |
14 | Ports
15 | -----
16 | - ``clk`` clock
17 | - ``aresetn`` asynchoronous active-low reset
18 | - ``q`` count (N bits)
19 |
20 | Source Code
21 | -----------
22 | .. literalinclude:: ../../libsv/counters/binary_counter.sv
23 | :language: systemverilog
24 | :linenos:
25 | :caption: binary_counter.sv
26 |
27 |
--------------------------------------------------------------------------------
/libsv/muxes/onehot_mux.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_MUXES_ONEHOT_MUX
2 | `define LIBSV_MUXES_ONEHOT_MUX
3 |
4 | module onehot_mux #(
5 | parameter int PORTS /* verilator public_flat_rd */ = 4,
6 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 8
7 | ) (
8 | input logic [PORTS*DATA_WIDTH-1:0] i_data,
9 | input logic [ PORTS-1:0] i_select,
10 | output logic [ DATA_WIDTH-1:0] o_data
11 | );
12 |
13 | always_comb begin
14 | o_data = '0;
15 | for (int i = 0; i < PORTS; ++i) begin
16 | o_data |= {(DATA_WIDTH) {i_select[i]}} & i_data[((i+1)*DATA_WIDTH-1)-:DATA_WIDTH];
17 | end
18 | end
19 |
20 | endmodule : onehot_mux
21 |
22 | `endif /* LIBSV_MUXES_ONEHOT_MUX */
23 |
--------------------------------------------------------------------------------
/docs/source/rotate.rst:
--------------------------------------------------------------------------------
1 | .. _rotate:
2 |
3 | ======
4 | Rotate
5 | ======
6 |
7 | The rotate module is a combinatorial block that implements a bit-wise left rotation of the
8 | given input data, ``i_data``, by the amount, ``i_amt``. For example, an ``i_data`` of
9 | ``0b10010`` with an ``i_amt`` of ``0b001`` would result in an ``o_data`` of ``0b00101``.
10 |
11 | Parameters
12 | ----------
13 | - ``DATA_WIDTH`` : data width in bits
14 |
15 | Ports
16 | -----
17 | - ``i_data`` input data
18 | - ``i_amt`` amount to rotate
19 | - ``o_data`` output data (rotated version of input)
20 |
21 | Source Code
22 | -----------
23 | .. literalinclude:: ../../libsv/bit_ops/rotate.sv
24 | :language: systemverilog
25 | :linenos:
26 | :caption: rotate.sv
27 |
--------------------------------------------------------------------------------
/docs/source/bcd_encoder.rst:
--------------------------------------------------------------------------------
1 | ===========
2 | BCD Encoder
3 | ===========
4 |
5 | The BCD (Binary-Coded Decimal) encoder is a combinatorial circuit that takes an ``N``-bit binary input, ``i_bin``, and converts it to a BCD value,
6 | ``o_bcd``, that is ``N + ceil{(N-4)/3} + 1`` bits long. It implements the conversion using the
7 | `double dabble `_ algorithm.
8 |
9 | Parameters
10 | ----------
11 | - ``N`` number of bits in binary input
12 |
13 | Ports
14 | -----
15 | - ``i_bin`` input binary value
16 | - ``o_bcd`` output BCD value
17 |
18 | Source Code
19 | -----------
20 | .. literalinclude:: ../../libsv/coders/bcd_encoder.sv
21 | :language: systemverilog
22 | :linenos:
23 | :caption: bcd_encoder.sv
24 |
--------------------------------------------------------------------------------
/libsv/coders/onehot_priority_encoder.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_CODERS_ONEHOT_PRIORITY_ENCODER
2 | `define LIBSV_CODERS_ONEHOT_PRIORITY_ENCODER
3 |
4 | module onehot_priority_encoder #(
5 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 4
6 | ) (
7 | input logic [DATA_WIDTH-1:0] i_data,
8 | output logic [DATA_WIDTH-1:0] o_data
9 | );
10 |
11 | always_comb begin
12 | bit stop;
13 | o_data = '0;
14 | stop = 1'b0;
15 |
16 | for (int i = 0; i < DATA_WIDTH; ++i) begin
17 | if (i_data[i] == 1'b1 && !stop) begin
18 | o_data[i] = 1'b1;
19 | stop = 1'b1;
20 | end
21 | end
22 | end
23 |
24 | endmodule : onehot_priority_encoder
25 |
26 | `endif /* LIBSV_CODERS_ONEHOT_PRIORITY_ENCODER */
27 |
--------------------------------------------------------------------------------
/tests/math/test_full_adder.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.triggers import Timer
3 | from utils import pytest_cocotb_run_test
4 |
5 |
6 | def test_full_adder(pytestconfig):
7 | """Pytest fixture for Full Adder test"""
8 | pytest_cocotb_run_test(pytestconfig, __name__)
9 |
10 |
11 | @cocotb.test()
12 | async def cocotb_test_full_adder(dut):
13 | """Full adder test"""
14 |
15 | for i in range(2**3):
16 | i_a = i & 1
17 | i_b = (i >> 1) & 1
18 | i_carry = (i >> 2) & 1
19 | o_sum = i_a ^ i_b ^ i_carry
20 | o_carry = ((i_a | i_b) & i_carry) | (i_a & i_b)
21 |
22 | dut.i_a.value = i_a
23 | dut.i_b.value = i_b
24 | dut.i_carry.value = i_carry
25 | await Timer(1)
26 | assert dut.o_sum == o_sum
27 | assert dut.o_carry == o_carry
28 |
--------------------------------------------------------------------------------
/tests/muxes/test_onehot_mux.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.triggers import Timer
3 | from utils import pytest_cocotb_run_test
4 |
5 |
6 | def test_onehot_mux(pytestconfig):
7 | """Pytest fixture for One-hot Mux test"""
8 | pytest_cocotb_run_test(pytestconfig, __name__)
9 |
10 |
11 | @cocotb.test()
12 | async def cocotb_test_onehot_mux(dut):
13 | """One-hot mux test"""
14 |
15 | ports = int(dut.PORTS)
16 | data_width = int(dut.DATA_WIDTH)
17 |
18 | # Generate counting pattern on input vectors
19 | dut_i_data = 0
20 | for i in range(ports):
21 | dut_i_data |= (i % data_width) << (i * data_width)
22 | dut.i_data.value = dut_i_data
23 |
24 | for i in range(ports):
25 | dut.i_select.value = 1 << i
26 | await Timer(1)
27 | assert i % data_width == int(dut.o_data)
28 |
--------------------------------------------------------------------------------
/docs/source/bcd_decoder.rst:
--------------------------------------------------------------------------------
1 | ===========
2 | BCD Decoder
3 | ===========
4 |
5 | The BCD (Binary-Coded Decimal) decoder is a combinatorial circuit takes a ``N`` decimal digit BCD input and converts it to a binary output.
6 | The BCD input , ``i_bcd``, is a ``4*N`` bit wide input vector where each 4 bits represents one decimal digit. The output binary value, ``o_bin``,
7 | is ``3*N + floor{(N+2)/3}`` bits long. It implements the conversion using the reverse `double dabble `_ algorithm.
8 |
9 | Parameters
10 | ----------
11 | - ``N`` number of digits in BCD input
12 |
13 | Ports
14 | -----
15 | - ``i_bcd`` input BCD value
16 | - ``o_bin`` output binary value
17 |
18 | Source Code
19 | -----------
20 | .. literalinclude:: ../../libsv/coders/bcd_decoder.sv
21 | :language: systemverilog
22 | :linenos:
23 | :caption: bcd_decoder.sv
24 |
--------------------------------------------------------------------------------
/libsv/synchronizers/synchronizer.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_SYNCHRONIZERS_SYNCHRONIZER
2 | `define LIBSV_SYNCHRONIZERS_SYNCHRONIZER
3 |
4 | module synchronizer #(
5 | parameter int FF_STAGES /* verilator public_flat_rd */ = 2
6 | ) (
7 | input logic i_clock,
8 | input logic i_aresetn,
9 | input logic i_data,
10 | output logic o_data
11 | );
12 |
13 | logic [FF_STAGES-1:0] sync_mem;
14 |
15 | always_ff @(posedge i_clock, negedge i_aresetn) begin
16 | if (!i_aresetn) begin
17 | sync_mem <= '0;
18 | end else begin
19 | sync_mem[0] <= i_data;
20 | for (int i = 1; i < FF_STAGES; ++i) begin
21 | sync_mem[i] <= sync_mem[i-1];
22 | end
23 | end
24 | end
25 |
26 | assign o_data = sync_mem[FF_STAGES-1];
27 |
28 | endmodule
29 |
30 | `endif /* LIBSV_SYNCHRONIZERS_SYNCHRONIZER */
31 |
--------------------------------------------------------------------------------
/docs/source/onehot_mux.rst:
--------------------------------------------------------------------------------
1 | .. _one-hot-mux:
2 |
3 | ===========
4 | One-hot Mux
5 | ===========
6 |
7 | A parameterized implementation of a one-hot mux. Takes ``PORTS`` input ports each of width
8 | ``DATA_WIDTH`` as a concatenated input vector, ``i_data``, and using the one-hot select input,
9 | ``i_select``, muxes the selected input port to the output, ``o_data``.
10 |
11 | Parameters
12 | ----------
13 | - ``PORTS`` number of input ports of each ``DATA_WIDTH`` bits
14 | - ``DATA_WIDTH`` data width in bits per input port
15 |
16 | Ports
17 | -----
18 | - ``i_data`` concatenated input vector (``PORTS*DATA_WIDTH`` bits)
19 | - ``i_select`` select (``PORTS`` bits)
20 | - ``o_data`` output vector (``DATA_WIDTH`` bits)
21 |
22 | Source Code
23 | -----------
24 | .. literalinclude:: ../../libsv/muxes/onehot_mux.sv
25 | :language: systemverilog
26 | :linenos:
27 | :caption: onehot_mux.sv
28 |
--------------------------------------------------------------------------------
/libsv/coders/priority_encoder.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_CODERS_PRIORITY_ENCODER
2 | `define LIBSV_CODERS_PRIORITY_ENCODER
3 |
4 | module priority_encoder #(
5 | parameter integer DATA_WIDTH /* verilator public_flat_rd */ = 4
6 | ) (
7 | input logic [ DATA_WIDTH-1:0] i_data,
8 | output logic [$clog2(DATA_WIDTH)-1:0] o_data,
9 | output logic o_valid
10 | );
11 |
12 | always_comb begin
13 | bit stop;
14 | o_data = '0;
15 | o_valid = 1'b0;
16 | stop = 1'b0;
17 |
18 | for (int i = 0; i < DATA_WIDTH; ++i) begin
19 | if (i_data[i] == 1'b1 && !stop) begin
20 | o_data = $clog2(DATA_WIDTH)'(i);
21 | o_valid = 1'b1;
22 | stop = 1'b1;
23 | end
24 | end
25 | end
26 |
27 | endmodule
28 |
29 | `endif /* LIBSV_CODERS_PRIORITY_ENCODER */
30 |
--------------------------------------------------------------------------------
/docs/source/sr_latch.rst:
--------------------------------------------------------------------------------
1 | ========
2 | SR Latch
3 | ========
4 |
5 | This module implements a `SR NOR latch `_.
6 |
7 | While ``s`` (set) and ``r`` (reset) are both low, ``q`` and ``q_n`` are maintained in a constant state, with ``q_n`` being
8 | the complement of ``q``.If ``s`` goes high while ``r`` is held low, then ``q`` is forced high. Similarly, if ``r`` goes high
9 | while ``s`` is held low, then ``q`` is forced low. Having both ``s`` and ``r`` high (``1``) is a restricted combination.
10 |
11 | Parameters
12 | ----------
13 | - None
14 |
15 | Ports
16 | -----
17 | - ``s`` set
18 | - ``r`` reset
19 | - ``q`` output
20 | - ``q_n`` complemented output
21 |
22 | Source Code
23 | -----------
24 | .. literalinclude:: ../../libsv/latches/sr_latch.sv
25 | :language: systemverilog
26 | :linenos:
27 | :caption: sr_latch.sv
28 |
--------------------------------------------------------------------------------
/libsv/coders/bcd_encoder.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_CODERS_BCD_ENCODER
2 | `define LIBSV_CODERS_BCD_ENCODER
3 |
4 | module bcd_encoder #(
5 | parameter integer N /* verilator public_flat_rd */ = 8
6 | ) (
7 | input logic [ N-1:0] i_bin,
8 | output logic [N+(N-4)/3:0] o_bcd
9 | );
10 |
11 | integer i, j;
12 |
13 | always_comb begin
14 | o_bcd = {{(N - 4) / 3 + 1{1'b0}}, i_bin}; // initialize with input vector in lower bits
15 | for (i = 0; i <= N - 4; i = i + 1) begin // iterate on structure depth
16 | for (j = 0; j <= i / 3; j = j + 1) begin // iterate on structure width
17 | if (o_bcd[N-i+4*j-:4] > 4) begin // if > 4
18 | o_bcd[N-i+4*j-:4] = o_bcd[N-i+4*j-:4] + 4'd3; // add 3
19 | end
20 | end
21 | end
22 | end
23 |
24 | endmodule
25 |
26 | `endif /* LIBSV_CODERS_BCD_ENCODER */
27 |
--------------------------------------------------------------------------------
/tests/synchronizers/test_synchronizer.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.clock import Clock
3 | from cocotb.triggers import FallingEdge
4 | from utils import pytest_cocotb_run_test
5 |
6 |
7 | def test_synchonrizer(pytestconfig):
8 | """Pytest fixture for Synchronizer test"""
9 | pytest_cocotb_run_test(pytestconfig, __name__)
10 |
11 |
12 | @cocotb.test()
13 | async def cocotb_test_synchonrizer(dut):
14 | """Synchronizer test"""
15 |
16 | ff_stages = int(dut.FF_STAGES)
17 |
18 | cocotb.start_soon(Clock(dut.i_clock, 2).start())
19 |
20 | dut.i_aresetn.value = 0
21 | await FallingEdge(dut.i_clock)
22 | dut.i_aresetn.value = 1
23 |
24 | sync_value = 1
25 | dut.i_data.value = sync_value
26 | for _ in range(ff_stages):
27 | assert dut.o_data.value != sync_value
28 | await FallingEdge(dut.i_clock)
29 |
30 | assert dut.o_data.value == sync_value
31 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/tests/counters/test_binary_counter.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.clock import Clock
3 | from cocotb.triggers import FallingEdge, RisingEdge
4 | from utils import pytest_cocotb_run_test
5 |
6 |
7 | def test_binary_counter(pytestconfig):
8 | """Pytest fixture for Binary Counter test"""
9 | pytest_cocotb_run_test(pytestconfig, __name__)
10 |
11 |
12 | @cocotb.test()
13 | async def cocotb_test_binary_counter(dut):
14 | """Counter test"""
15 |
16 | n = int(dut.N)
17 |
18 | cocotb.start_soon(Clock(dut.clk, 2).start())
19 |
20 | # reset
21 | dut.aresetn.value = 0
22 | await FallingEdge(dut.clk)
23 | dut.aresetn.value = 1
24 |
25 | # increment through all possible counter states
26 | for i in range(2**n):
27 | await RisingEdge(dut.clk)
28 | assert int(dut.q) == i
29 |
30 | # increment once more and check roll-over condition
31 | await RisingEdge(dut.clk)
32 | assert int(dut.q) == 0
33 |
--------------------------------------------------------------------------------
/docs/source/onehot_priority_encoder.rst:
--------------------------------------------------------------------------------
1 | .. _one-hot-priority-encoder:
2 |
3 | ========================
4 | One-hot Priority Encoder
5 | ========================
6 |
7 | The One-hot Priority Encoder is a parameterized combinatorial implementation that takes input data,
8 | ``i_data``, and outputs a one-hot priority-encoded output, ``o_data``, where the least significant bit
9 | (bit 0) is the highest priority bit and the most significant bit (bit ``DATA_WIDTH-1``) is the lowest
10 | priority bit. For example, the input ``0b1001`` would result in the output ``0b0001``.
11 |
12 | Parameters
13 | ----------
14 | - ``DATA_WIDTH`` : data width in bits
15 |
16 | Ports
17 | -----
18 | - ``i_data`` : input data
19 | - ``o_data`` : one-hot priority-encoded output data
20 |
21 | Source Code
22 | -----------
23 | .. literalinclude:: ../../libsv/coders/onehot_priority_encoder.sv
24 | :language: systemverilog
25 | :linenos:
26 | :caption: onehot_priority_encoder.sv
27 |
--------------------------------------------------------------------------------
/tests/coders/test_onehot_priority_encoder.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.triggers import Timer
3 | from utils import pytest_cocotb_run_test
4 |
5 |
6 | def test_onehot_priority_encoder(pytestconfig):
7 | """Pytest fixture for Priority Encoder test"""
8 | pytest_cocotb_run_test(pytestconfig, __name__)
9 |
10 |
11 | @cocotb.test()
12 | async def cocotb_test_onehot_priority_encoder(dut):
13 | """One-hot Priority Encoder test"""
14 |
15 | data_width = int(dut.DATA_WIDTH)
16 |
17 | for i in range(2**data_width):
18 | dut.i_data.value = i # drive input
19 |
20 | await Timer(1)
21 |
22 | result = onehot_priority_encode(i, data_width)
23 | assert dut.o_data == result # check output
24 |
25 |
26 | def onehot_priority_encode(x: int, data_width: int) -> int:
27 |
28 | test = 0
29 | for shift_amt in range(data_width):
30 |
31 | test = x & (1 << shift_amt)
32 |
33 | if test > 0:
34 | return test
35 |
36 | return 0
37 |
--------------------------------------------------------------------------------
/docs/source/priority_encoder.rst:
--------------------------------------------------------------------------------
1 | ================
2 | Priority Encoder
3 | ================
4 |
5 | The Priority Encoder is a parameterized combinatorial implementation that takes input data,
6 | ``i_data``, and outputs a binary priority-encoded output, ``o_data``, where the least significant bit
7 | (bit 0) is the highest priority bit and the most significant bit (bit ``DATA_WIDTH-1``) is the lowest
8 | priority bit. For example, the input ``0b1001`` would result in the output ``0b00`` with the output
9 | valid signal being ``1``. Because the input can be all zeros, it's possible the output is not valid
10 | which is provided by the ``o_valid`` signal.
11 |
12 | Parameters
13 | ----------
14 | - ``DATA_WIDTH`` : data width in bits
15 |
16 | Ports
17 | -----
18 | - ``i_data`` : input data
19 | - ``o_data`` : binary priority-encoded output data
20 | - ``o_valid`` : valid output
21 |
22 | Source Code
23 | -----------
24 | .. literalinclude:: ../../libsv/coders/priority_encoder.sv
25 | :language: systemverilog
26 | :linenos:
27 | :caption: priority_encoder.sv
28 |
--------------------------------------------------------------------------------
/libsv/coders/bcd_decoder.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_CODERS_BCD_DECODER
2 | `define LIBSV_CODERS_BCD_DECODER
3 |
4 | module bcd_decoder #(
5 | parameter integer N /* verilator public_flat_rd */ = 3
6 | ) (
7 | input logic [ 4*N-1:0] i_bcd,
8 | output logic [3*N+(N+2)/3-1:0] o_bin
9 | );
10 |
11 | integer i, j;
12 |
13 | logic [4*N-1:0] temp; // create temp vector as wide as i_bcd to hold intermediate values
14 | always_comb begin
15 | temp = i_bcd; // initialize with input vector
16 | for (i = 0; i < 4 * (N - 1); i = i + 1) begin // iterate on structure depth
17 | for (j = 0; j < N - i / 4 - 1; j = j + 1) begin // iterate on structure width
18 | if (temp[4+i+4*j-:4] > 7) begin // if > 7
19 | temp[4+i+4*j-:4] = temp[4+i+4*j-:4] - 4'd3; // subtract three
20 | end
21 | end
22 | end
23 | o_bin = temp[3*N+(N+2)/3-1:0]; // truncate final result in temp vector to get o_bin
24 | end
25 |
26 | endmodule
27 |
28 | `endif /* LIBSV_CODERS_BCD_DECODER */
29 |
--------------------------------------------------------------------------------
/tests/coders/test_priority_encoder.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.triggers import Timer
3 | from utils import pytest_cocotb_run_test
4 |
5 |
6 | def test_priority_encoder(pytestconfig):
7 | """Pytest fixture for Priority Encoder test"""
8 | pytest_cocotb_run_test(pytestconfig, __name__)
9 |
10 |
11 | @cocotb.test()
12 | async def cocotb_test_priority_encoder(dut):
13 | """Priority Encoder test"""
14 |
15 | data_width = int(dut.DATA_WIDTH)
16 |
17 | for i in range(2**data_width):
18 | dut.i_data.value = i # drive input
19 |
20 | await Timer(1)
21 |
22 | # check valid signal
23 | if i == 0:
24 | assert dut.o_valid == 0
25 | else:
26 | result = priority_encode(i, data_width)
27 | assert dut.o_valid == 1
28 | assert dut.o_data == result # check output
29 |
30 |
31 | def priority_encode(x: int, data_width: int) -> int:
32 |
33 | test = 0
34 | for shift_amt in range(data_width):
35 |
36 | test = x & (1 << shift_amt)
37 |
38 | if test > 0:
39 | return shift_amt
40 |
41 | return 0
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-2021 Ben Sampson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tests/coders/test_gray_encoder.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.triggers import Timer
3 | from cocotb.log import SimLog
4 | from utils import pytest_cocotb_run_test
5 |
6 |
7 | def test_gray_encoder(pytestconfig):
8 | """Pytest fixture for Gray Encoder test"""
9 | pytest_cocotb_run_test(pytestconfig, __name__)
10 |
11 |
12 | @cocotb.test()
13 | async def cocotb_test_gray_encoder(dut):
14 | """Gray Encoder test"""
15 |
16 | log = SimLog("cocotb.test_gray_encoder")
17 |
18 | data_width = int(dut.DATA_WIDTH)
19 |
20 | for i in range(2**data_width):
21 | dut.i_bin.value = i
22 | await Timer(1)
23 |
24 | try:
25 | assert int(dut.o_gray.value) == gray_encode(i, data_width)
26 | except AssertionError as e:
27 | log.info(
28 | f"i_bin = {dut.i_bin.value}, o_gray = {dut.o_gray.value}, "
29 | + "gray_encode = "
30 | + format(gray_encode(i, data_width), f"0{data_width}b")
31 | )
32 | raise e
33 |
34 |
35 | def gray_encode(binary: int, data_width: int) -> int:
36 |
37 | gray = binary & (1 << (data_width - 1))
38 | for i in range(data_width - 1):
39 | gray |= (binary ^ (binary >> 1)) & (1 << i)
40 |
41 | return gray
42 |
--------------------------------------------------------------------------------
/tests/coders/test_gray_decoder.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.triggers import Timer
3 | from cocotb.log import SimLog
4 | from utils import pytest_cocotb_run_test
5 |
6 |
7 | def test_gray_decoder(pytestconfig):
8 | """Pytest fixture for Gray Decoder test"""
9 | pytest_cocotb_run_test(pytestconfig, __name__)
10 |
11 |
12 | @cocotb.test()
13 | async def cocotb_test_gray_decoder(dut):
14 | """Gray Decoder test"""
15 |
16 | log = SimLog("cocotb.test_gray_decoder")
17 |
18 | data_width = int(dut.DATA_WIDTH)
19 |
20 | for i in range(2**data_width):
21 |
22 | dut.i_gray.value = i
23 | await Timer(1)
24 |
25 | try:
26 | assert int(dut.o_bin.value) == gray_decode(i, data_width)
27 | except AssertionError as e:
28 | log.info(
29 | f"i_gray = {dut.i_gray.value}, o_bin = {dut.o_bin.value}, "
30 | + "gray_decode = "
31 | + format(gray_decode(i, data_width), f"0{data_width}b")
32 | )
33 | raise e
34 |
35 |
36 | def gray_decode(gray: int, data_width: int) -> int:
37 |
38 | binary = gray & (1 << (data_width - 1))
39 | for i in range(data_width - 1):
40 | temp = gray & (1 << i)
41 | for j in range(i + 1, data_width):
42 | temp ^= gray >> (j - i)
43 | binary |= temp & (1 << i)
44 |
45 | return binary
46 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "libsv"
3 | version = "0.2.1"
4 | description = "An open source, parameterized SystemVerilog hardware IP library"
5 | authors = ["Ben Sampson "]
6 | maintainers = ["Ben Sampson "]
7 | license = "MIT"
8 | repository = "https://github.com/bensampson5/libsv"
9 | homepage = "https://libsv.readthedocs.io/en/latest/"
10 | documentation = "https://libsv.readthedocs.io/en/latest/"
11 | keywords = ["SystemVerilog", "Verilog", "Hardware", "IP", "Cocotb"]
12 | readme = "README.rst"
13 | classifiers = [
14 | "Topic :: System :: Hardware",
15 | "Topic :: Scientific/Engineering",
16 | "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)"
17 | ]
18 |
19 | [tool.poetry.urls]
20 | "Bug Tracker" = "https://github.com/bensampson5/libsv/issues"
21 |
22 | [tool.poetry.dependencies]
23 | python = "^3.7,<4.0.0"
24 |
25 | [tool.poetry.dev-dependencies]
26 | cocotb = "^1.6.0"
27 | pytest = "^6.2.5"
28 | Sphinx = "^4.2.0"
29 | sphinx-rtd-theme = "^1.0.0"
30 | flake8 = "^4.0.1"
31 | black = "^22.3.0"
32 | click = "^8.0.3"
33 | colorama = "^0.4.4"
34 | cocotb-test = "^0.2.1"
35 | PyYAML = "^6.0"
36 |
37 | [build-system]
38 | requires = ["poetry-core>=1.0.0"]
39 | build-backend = "poetry.core.masonry.api"
40 |
41 | [tool.pytest.ini_options]
42 | testpaths = ["tests"]
43 | log_cli = true
44 |
45 | [tool.black]
46 | line-length = 88
47 |
--------------------------------------------------------------------------------
/tests/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | from pathlib import Path
3 | from cocotb_test.simulator import run
4 |
5 |
6 | def pytest_cocotb_run_test(pytestconfig, test_name):
7 | os.environ["SIM"] = "verilator" # always use verilator as HDL simulator
8 | proj_path = Path(pytestconfig.rootpath) # capture top-level project path
9 |
10 | # get top level module name from file test name
11 | top_level = test_name.replace("test_", "")
12 |
13 | # determine verilog sources from top_level module
14 | src_dir = proj_path / "libsv"
15 | top_level_sv = list(src_dir.glob(f"**/{top_level}.sv"))
16 | if len(top_level_sv) == 1:
17 | top_level_sv = top_level_sv[0]
18 | with open(top_level_sv, "r") as f:
19 |
20 | found_top_level_module = False
21 | while not found_top_level_module:
22 | line = f.readline()
23 | if line.startswith(f"module {top_level} "):
24 | found_top_level_module = True
25 |
26 | if not found_top_level_module:
27 | raise RuntimeError("Could not find top level module")
28 | else:
29 | raise RuntimeError("Too many sv top level files")
30 |
31 | # determine build directory
32 | build_dir = Path(str(top_level_sv.parent).replace("libsv", "build"))
33 |
34 | # call to cocotb_test.simulator.run
35 | run(
36 | verilog_sources=[top_level_sv],
37 | toplevel=top_level,
38 | module=test_name,
39 | includes=[proj_path],
40 | sim_build=build_dir,
41 | waves=True,
42 | )
43 |
44 | # rename waveform file
45 | wavefile = build_dir / "dump.fst"
46 | if wavefile.exists():
47 | wavefile.rename(build_dir / f"{test_name}.fst")
48 |
--------------------------------------------------------------------------------
/tests/bit_ops/test_rotate.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.triggers import Timer
3 | from cocotb.log import SimLog
4 | from utils import pytest_cocotb_run_test
5 |
6 |
7 | def test_rotate(pytestconfig):
8 | """Pytest fixture for Rotate test"""
9 | pytest_cocotb_run_test(pytestconfig, __name__)
10 |
11 |
12 | @cocotb.test()
13 | async def cocotb_test_rotate(dut):
14 | """Rotate test"""
15 |
16 | log = SimLog("cocotb.test_rotate")
17 |
18 | data_width = int(dut.DATA_WIDTH.value)
19 | amt_bits = len(dut.i_amt.value)
20 |
21 | for value in range(2**data_width):
22 |
23 | for amt in range(2**amt_bits):
24 |
25 | dut.i_data.value = value
26 | dut.i_amt.value = amt
27 | await Timer(1)
28 |
29 | o_data_expected = 0
30 | try:
31 | o_data_expected = do_rotate(value, amt, data_width)
32 | assert o_data_expected == dut.o_data.value
33 | log.debug(
34 | f"PASSED: i_data = 0b{dut.i_data.value}, i_amt = "
35 | f"0b{dut.i_amt.value}, o_data = 0b{dut.o_data.value}, "
36 | "o_data_expected = 0b" + format(o_data_expected, f"0{data_width}b")
37 | )
38 | except AssertionError as e:
39 | log.critical(
40 | f"FAILED: i_data = 0b{dut.i_data.value}, i_amt = "
41 | f"0b{dut.i_amt.value}, o_data = 0b{dut.o_data.value}, "
42 | "o_data_expected = 0b" + format(o_data_expected, f"0{data_width}b")
43 | )
44 | raise e
45 |
46 |
47 | def do_rotate(value: int, amt: int, data_width: int):
48 | data_width_mask = 2**data_width - 1
49 | adj_amt = amt % data_width # adjust amount if greater than data_width
50 | return ((value << adj_amt) & data_width_mask) | (
51 | (value >> (data_width - adj_amt)) & data_width_mask
52 | )
53 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 |
13 | import os
14 | import sys
15 |
16 | sys.path.insert(0, os.path.abspath("."))
17 |
18 |
19 | # -- Project information -----------------------------------------------------
20 |
21 | project = "LibSV"
22 | copyright = "2020-2021, Ben Sampson"
23 | author = "Ben Sampson"
24 |
25 |
26 | # -- General configuration ---------------------------------------------------
27 |
28 | # Add any Sphinx extension module names here, as strings. They can be
29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
30 | # ones.
31 | extensions = ["sphinx_rtd_theme"]
32 |
33 | # Add any paths that contain templates here, relative to this directory.
34 | templates_path = ["_templates"]
35 |
36 | # List of patterns, relative to source directory, that match files and
37 | # directories to ignore when looking for source files.
38 | # This pattern also affects html_static_path and html_extra_path.
39 | exclude_patterns = []
40 |
41 |
42 | # -- Options for HTML output -------------------------------------------------
43 |
44 | # The theme to use for HTML and HTML Help pages. See the documentation for
45 | # a list of builtin themes.
46 | #
47 | html_theme = "sphinx_rtd_theme"
48 |
49 | # Add any paths that contain custom static files (such as style sheets) here,
50 | # relative to this directory. They are copied after the builtin static files,
51 | # so a file named "default.css" will overwrite the builtin "default.css".
52 | html_static_path = ["_static"]
53 |
54 | html_logo = "_static/libsv_logo.svg"
55 |
--------------------------------------------------------------------------------
/tests/fifos/test_skid_buffer.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.clock import Clock
3 | from cocotb.triggers import FallingEdge
4 | from utils import pytest_cocotb_run_test
5 |
6 |
7 | def test_skid_buffer(pytestconfig):
8 | """Pytest fixture for Skid Buffer test"""
9 | pytest_cocotb_run_test(pytestconfig, __name__)
10 |
11 |
12 | @cocotb.test()
13 | async def cocotb_test_skid_buffer(dut):
14 | """Skid Buffer test"""
15 |
16 | data_width = int(dut.DATA_WIDTH)
17 |
18 | cocotb.start_soon(Clock(dut.i_clock, 2).start())
19 |
20 | # reset
21 | dut.i_aresetn.value = 0
22 | await FallingEdge(dut.i_clock)
23 | dut.i_aresetn.value = 1
24 |
25 | # generate data and have both input and output interfaces
26 | # be ready.
27 | i_data = 0
28 | o_data = 0
29 | dut.i_data.value = i_data
30 | dut.i_input_valid.value = 1
31 | dut.i_output_ready.value = 1
32 |
33 | # stream at full rate for 4 transactions
34 | for _ in range(4):
35 | await FallingEdge(dut.i_clock)
36 | o_data = i_data
37 | assert int(dut.o_data.value) == o_data # check output data
38 | i_data = (i_data + 1) % (2**data_width)
39 | dut.i_data.value = i_data # drive next input data
40 |
41 | # disable output ready so that skid buffer gets full
42 | dut.i_output_ready.value = 0
43 | await FallingEdge(dut.i_clock)
44 | for _ in range(4):
45 | await FallingEdge(dut.i_clock)
46 | assert int(dut.o_data.value) == o_data
47 |
48 | # re-enable output ready but disable input valid so that
49 | # buffer empties
50 | dut.i_output_ready.value = 1
51 | dut.i_input_valid.value = 0
52 | for _ in range(4):
53 | await FallingEdge(dut.i_clock)
54 | if o_data < i_data:
55 | o_data += 1
56 | assert int(dut.o_data.value) == o_data # check output data
57 |
58 | # continue streaming for another few transactions
59 | dut.i_input_valid.value = 1
60 | dut.i_output_ready.value = 1
61 | for _ in range(4):
62 | await FallingEdge(dut.i_clock)
63 | o_data = i_data
64 | assert int(dut.o_data.value) == o_data # check output data
65 | i_data = (i_data + 1) % (2**data_width)
66 | dut.i_data.value = i_data # drive next input data
67 |
68 | await FallingEdge(dut.i_clock)
69 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:jammy
2 |
3 | WORKDIR /tmp
4 |
5 | # Baseline apt-get installs
6 | ARG DEBIAN_FRONTEND=noninteractive
7 | RUN apt-get update \
8 | && apt-get install --no-install-recommends -y \
9 | apt-transport-https \
10 | autoconf \
11 | bc \
12 | bison \
13 | ca-certificates \
14 | ccache \
15 | curl \
16 | flex \
17 | g++ \
18 | git \
19 | gnupg \
20 | help2man \
21 | libfl2 \
22 | libfl-dev \
23 | libgoogle-perftools-dev \
24 | make \
25 | numactl \
26 | perl \
27 | perl-doc \
28 | python3-dev \
29 | python3-pip \
30 | wget \
31 | zlib1g \
32 | zlib1g-dev \
33 | && apt-get clean \
34 | && rm -rf /var/lib/apt/lists/*
35 |
36 | # Add Bazel distribution URI as a package source
37 | RUN curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor >bazel-archive-keyring.gpg \
38 | && mv bazel-archive-keyring.gpg /usr/share/keyrings \
39 | && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/bazel-archive-keyring.gpg] https://storage.googleapis.com/bazel-apt stable jdk1.8" | tee /etc/apt/sources.list.d/bazel.list
40 |
41 | # Install Bazel
42 | RUN apt-get update \
43 | && apt-get install --no-install-recommends -y \
44 | bazel \
45 | && apt-get clean \
46 | && rm -rf /var/lib/apt/lists/*
47 |
48 | # Install python packages using poetry
49 | RUN pip3 install poetry
50 | COPY pyproject.toml ./
51 | RUN poetry config virtualenvs.create false
52 | RUN poetry install -n --no-ansi
53 |
54 | # Build and install Verilator v5.024 from source
55 | ARG REPO=https://github.com/verilator/verilator
56 | ARG TAG=v5.024
57 | RUN git clone --depth 1 --branch "${TAG}" "${REPO}" verilator \
58 | && cd verilator \
59 | && autoconf \
60 | && ./configure \
61 | && make -j$(nproc) \
62 | && make install \
63 | && cd .. \
64 | && rm -rf verilator
65 |
66 | # Install Verible
67 | ARG VERIBLE_URL=https://github.com/chipsalliance/verible/releases/download/v0.0-3648-g5ef1624a/verible-v0.0-3648-g5ef1624a-linux-static-x86_64.tar.gz
68 | RUN wget ${VERIBLE_URL} -O verible.tar.gz \
69 | && mkdir verible \
70 | && tar -xf verible.tar.gz -C verible --strip-components=1 \
71 | && cp -r verible/bin/* /usr/local/bin \
72 | && rm -rf verible verible.tar.gz
73 |
74 | WORKDIR /root
75 | CMD /bin/bash
--------------------------------------------------------------------------------
/tests/fifos/test_sync_fifo.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.clock import Clock
3 | from cocotb.triggers import FallingEdge
4 | from utils import pytest_cocotb_run_test
5 |
6 |
7 | def test_sync_fifo(pytestconfig):
8 | """Pytest fixture for Sync Fifo test"""
9 | pytest_cocotb_run_test(pytestconfig, __name__)
10 |
11 |
12 | @cocotb.test()
13 | async def cocotb_test_sync_fifo(dut):
14 | """Sync Fifo test"""
15 |
16 | data_width = int(dut.DATA_WIDTH)
17 | fifo_depth = int(dut.FIFO_DEPTH)
18 |
19 | cocotb.start_soon(Clock(dut.i_clock, 2).start())
20 |
21 | # reset
22 | dut.i_aresetn.value = 0
23 | await FallingEdge(dut.i_clock)
24 | dut.i_aresetn.value = 1
25 |
26 | # fill up fifo completely
27 | dut.i_wr_en.value = 1
28 | for i in range(1, fifo_depth + 1):
29 | dut.i_data.value = i % data_width
30 | await FallingEdge(dut.i_clock)
31 |
32 | assert int(dut.o_full.value) == 1
33 | assert int(dut.o_empty.value) == 0
34 |
35 | # read out everything from fifo completely
36 | dut.i_wr_en.value = 0
37 | dut.i_rd_en.value = 1
38 | for i in range(1, fifo_depth + 1):
39 | assert int(dut.o_data.value) == i % data_width
40 | await FallingEdge(dut.i_clock)
41 |
42 | assert int(dut.o_full.value) == 0
43 | assert int(dut.o_empty.value) == 1
44 |
45 | # test streaming with 1 entry in fifo
46 | for i in range(1, 6):
47 | if i == 1:
48 | dut.i_wr_en.value = 1
49 | dut.i_data.value = i % data_width
50 | await FallingEdge(dut.i_clock)
51 | dut.i_rd_en.value = 1
52 | else:
53 | assert int(dut.o_data.value) == (i - 1) % data_width
54 | dut.i_data.value = i % data_width
55 | await FallingEdge(dut.i_clock)
56 |
57 | assert int(dut.o_empty.value) == 0 # never is empty
58 |
59 | # test streaming with fifo_depth-1 entries in fifo
60 | dut.i_wr_en.value = 1
61 | dut.i_rd_en.value = 0
62 | dut.i_aresetn.value = 0
63 | await FallingEdge(dut.i_clock)
64 | dut.i_aresetn.value = 1
65 | for i in range(1, fifo_depth + 5):
66 | if i < fifo_depth: # fill up fifo
67 | dut.i_data.value = i % data_width
68 | await FallingEdge(dut.i_clock)
69 | if i == fifo_depth - 1:
70 | dut.i_rd_en.value = 1
71 | else:
72 | assert int(dut.o_data.value) == (i - (fifo_depth - 1)) % data_width
73 | dut.i_data.value = i % data_width
74 | await FallingEdge(dut.i_clock)
75 |
76 | assert int(dut.o_full.value) == 0 # never is full
77 |
78 | await FallingEdge(dut.i_clock)
79 |
--------------------------------------------------------------------------------
/docs/source/skid_buffer.rst:
--------------------------------------------------------------------------------
1 | .. _skid-buffer:
2 |
3 | ===========
4 | Skid Buffer
5 | ===========
6 |
7 | A skid buffer is used to break apart a combinatorial path between a sender and receiver interface by registering
8 | the data and handshake signals. The skid buffer uses two buffer entries, a main buffer and a "skid" buffer, that
9 | allows it to maintain the full throughput, thus pipelining the data path. Because there is a one clock cycle
10 | latency through the skid buffer for backpressuring the sender if it is backpressured by the receiver, the "skid"
11 | buffer is needed to absorb one extra transaction from the sender, before it can actually backpressure the sender.
12 | Skid buffers are commonly used for pipelining and resolving timing issues by breaking long combinatorial paths
13 | between a sender and receiver. Because there are only two buffer registers, it is also a very lightweight
14 | solution.
15 |
16 | This skid buffer implementation also provides two additional output status signals, ``o_accept`` and ``o_transmit``,
17 | which indicate when a transaction is successfully accepted from the input interface or successfully transmitted to
18 | the output interface. These two additional signals are provided so that a parent module can keep track of
19 | when the skid buffer is accepting or transmitting data. For example, this could be useful if the parent module
20 | needs that information for some of its own control logic or if it wants to keep track of how many transcations
21 | have been accepted and transmitted by the skid buffer.
22 |
23 |
24 | Parameters
25 | ----------
26 | - ``DATA_WIDTH`` : data width in bits
27 |
28 | Ports
29 | -----
30 | - ``i_clock`` : input clock
31 | - ``i_aresetn`` : asynchronous active-low reset
32 | - ``i_clear`` : synchronous clear
33 | - ``i_data`` : input data
34 | - ``i_input_valid`` : valid signal from input interface. Used with o_input_ready to handshake on input interface
35 | - ``i_output_ready`` : ready signal from output interface. Used with o_output_valid to handshake on output interface
36 | - ``o_data`` : output data
37 | - ``o_output_valid`` : valid signal from output interface. Used with i_output_ready to handshake on output interface
38 | - ``o_input_ready`` : ready signal from input interface. Used with i_input_valid to handshake on input interface
39 | - ``o_accept`` : accept status signal that indicates whenever input data is accepted by the skid buffer
40 | - ``o_transmit`` : transmit status signal that indicates whenever output data is transmitted by the skid buffer
41 |
42 | Source Code
43 | -----------
44 | .. literalinclude:: ../../libsv/fifos/skid_buffer.sv
45 | :language: systemverilog
46 | :linenos:
47 | :caption: skid_buffer.sv
48 |
--------------------------------------------------------------------------------
/libsv/fifos/sync_fifo.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_FIFOS_SYNC_FIFO
2 | `define LIBSV_FIFOS_SYNC_FIFO
3 |
4 | module sync_fifo #(
5 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 8,
6 | parameter int FIFO_DEPTH /* verilator public_flat_rd */ = 4
7 | ) (
8 | input logic i_clock,
9 | input logic i_aresetn,
10 | input logic i_wr_en,
11 | input logic i_rd_en,
12 | input logic [DATA_WIDTH-1:0] i_data,
13 | output logic [DATA_WIDTH-1:0] o_data,
14 | output logic o_full,
15 | output logic o_empty
16 | );
17 |
18 | typedef enum logic [2:0] {
19 | EMPTY = 3'b001,
20 | BUSY = 3'b010,
21 | FULL = 3'b100
22 | } state_t;
23 |
24 | state_t state, next_state;
25 | logic write, read; // variables that hold if write or read is happening
26 | logic [$clog2(FIFO_DEPTH)-1:0] wr_addr, rd_addr;
27 | logic [$clog2(FIFO_DEPTH)-1:0] next_wr_addr, next_rd_addr;
28 | logic [FIFO_DEPTH-1:0][DATA_WIDTH-1:0] fifo_mem;
29 |
30 | // Logic for status outputs empty and full
31 | always_ff @(posedge i_clock, negedge i_aresetn) begin
32 | if (!i_aresetn) begin
33 | o_empty <= 1'b1;
34 | o_full <= 1'b0;
35 | end else begin
36 | o_empty <= next_state == EMPTY;
37 | o_full <= next_state == FULL;
38 | end
39 | end
40 |
41 | always_comb begin : next_state_logic
42 | write = i_wr_en && (state != FULL);
43 | read = i_rd_en && (state != EMPTY);
44 |
45 | unique case (state)
46 | EMPTY: begin
47 | next_state = EMPTY;
48 | if (write) next_state = BUSY;
49 | end
50 | BUSY: begin
51 | next_state = BUSY;
52 | if (read && !write && next_rd_addr == wr_addr) next_state = EMPTY;
53 | else if (!read && write && next_wr_addr == rd_addr) next_state = FULL;
54 | end
55 | FULL: begin
56 | next_state = FULL;
57 | if (read) next_state = BUSY;
58 | end
59 | default: next_state = EMPTY;
60 | endcase
61 | end : next_state_logic
62 |
63 | always_ff @(posedge i_clock, negedge i_aresetn) begin : current_state_logic
64 | if (!i_aresetn) begin
65 | state <= EMPTY;
66 | end else begin
67 | state <= next_state;
68 | end
69 | end : current_state_logic
70 |
71 | always_comb begin : next_wr_rd_addr_logic
72 | next_wr_addr = wr_addr == $bits(wr_addr)'(FIFO_DEPTH - 1) ? '0 : wr_addr + 1'b1;
73 | next_rd_addr = rd_addr == $bits(rd_addr)'(FIFO_DEPTH - 1) ? '0 : rd_addr + 1'b1;
74 | end : next_wr_rd_addr_logic
75 |
76 | always_ff @(posedge i_clock, negedge i_aresetn) begin : update_rd_wr_addr
77 | if (!i_aresetn) begin
78 | rd_addr <= '0;
79 | wr_addr <= '0;
80 | end else begin
81 | wr_addr <= write ? next_wr_addr : wr_addr;
82 | rd_addr <= read ? next_rd_addr : rd_addr;
83 | end
84 | end : update_rd_wr_addr
85 |
86 | always_ff @(posedge i_clock, negedge i_aresetn) begin : data_path_logic
87 | if (!i_aresetn) begin
88 | fifo_mem <= '0;
89 | end else begin
90 | if (write) fifo_mem[wr_addr] <= i_data;
91 | end
92 | end : data_path_logic
93 |
94 | assign o_data = fifo_mem[rd_addr];
95 |
96 | endmodule
97 |
98 | `endif /* LIBSV_FIFOS_SYNC_FIFO */
99 |
--------------------------------------------------------------------------------
/tests/arbiters/test_ring_arbiter.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.clock import Clock
3 | from cocotb.triggers import FallingEdge
4 | from cocotb.log import SimLog
5 | from utils import pytest_cocotb_run_test
6 |
7 |
8 | def test_ring_arbiter(pytestconfig):
9 | """Pytest fixture for Ring Arbiter test"""
10 | pytest_cocotb_run_test(pytestconfig, __name__)
11 |
12 |
13 | @cocotb.test()
14 | async def cocotb_test_ring_arbiter(dut):
15 | """Ring Arbiter test"""
16 |
17 | log = SimLog("cocotb.test_ring_arbiter")
18 |
19 | ports = int(dut.PORTS)
20 | data_width = int(dut.DATA_WIDTH)
21 |
22 | cocotb.start_soon(Clock(dut.i_clock, 2).start())
23 |
24 | # reset
25 | dut.i_aresetn.value = 0
26 | await FallingEdge(dut.i_clock)
27 | dut.i_aresetn.value = 1
28 |
29 | # setup i_data
30 | i_data_array = [i & (2**data_width - 1) for i in range(ports)]
31 | i_data = 0
32 | for i in range(ports):
33 | i_data |= i << (data_width * i)
34 | log.info("i_data = 0x" + format(i_data, f"0{ports*data_width//4}x"))
35 | log.info(f"i_data_array = {i_data_array}")
36 | dut.i_data.value = i_data
37 | dut.i_input_valid.value = 2**ports - 1
38 |
39 | # Scenario 1: Have all ports have data ready for the arbiter and
40 | # have output interface be ready to receive data from arbiter. This
41 | # will test the ring arbiter at max throughput
42 | dut.i_clear.value = 1
43 | await FallingEdge(dut.i_clock)
44 | dut.i_clear.value = 0
45 | dut.i_output_ready.value = 1
46 | for i in range(4 * ports):
47 | await FallingEdge(dut.i_clock)
48 | if i > 0:
49 | assert int(dut.o_data.value) == i_data_array[(i % len(i_data_array)) - 1]
50 |
51 | # Scenario 2: Have a port drop out but still maintain max throughput
52 | # of output interface
53 | dut.i_input_valid.value = 2**ports - 2 # port 0 drops out
54 | i_data_array_s2 = i_data_array[1:]
55 | for i in range(4 * (ports - 1)):
56 | await FallingEdge(dut.i_clock)
57 | assert int(dut.o_data.value) == i_data_array_s2[(i % len(i_data_array_s2)) - 1]
58 |
59 | # Scenario 3: Have the output interface backpressure the ring arbiter
60 | # which should cause it to fill up and backpressure the input interface
61 | dut.i_output_ready.value = 0
62 | prev_i_data = int(dut.sb.i_data.value)
63 | prev_o_data = int(dut.o_data.value)
64 | await FallingEdge(dut.i_clock)
65 | # check that output is being held even though input is still accepted
66 | current_i_data = int(dut.sb.i_data.value)
67 | assert current_i_data == i_data_array_s2[0] and current_i_data != prev_i_data
68 | assert int(dut.o_data.value) == prev_o_data
69 | prev_i_data = int(dut.sb.i_data.value) # update prev_i_data
70 | await FallingEdge(dut.i_clock)
71 | # check that now both input and output are being held
72 | assert int(dut.sb.i_data.value) == prev_i_data
73 | assert int(dut.o_data.value) == prev_o_data
74 |
75 | # Scenario 4: Have all ports drop out but release the backpressing of the
76 | # ring arbiter by the output interface and validate the ring arbiter empties
77 | dut.i_input_valid.value = 0 # all ports drop out
78 | dut.i_output_ready.value = 1 # re-enable output interface
79 | await FallingEdge(dut.i_clock) # ring arbiter empties skid buffer
80 | current_o_data = int(dut.o_data.value)
81 | assert current_o_data != prev_o_data and current_o_data == i_data_array_s2[-1]
82 | prev_o_data = current_o_data
83 | await FallingEdge(dut.i_clock) # ring arbiter empties last entry
84 | assert dut.o_output_valid.value == 0
85 |
--------------------------------------------------------------------------------
/docs/source/ring_arbiter.rst:
--------------------------------------------------------------------------------
1 | .. _ring-arbiter:
2 |
3 | ============
4 | Ring Arbiter
5 | ============
6 |
7 | A ring arbiter is a many-to-one flow control block that accepts transactions from 2 or more input ports and
8 | provides that data to an output interface. If more than 1 input port is ready to send data, the ring arbiter
9 | will arbitrate between all active input ports by accepting 1 transaction from each input port, in port order,
10 | so that no input port is allowed to transmit two transactions in a row as long as there is at least one
11 | other input port that is waiting to send a transaction.
12 |
13 | This implementation is pipelined from input to output so that back-to-back transfers are achieved, thus
14 | allowing for sustained maximum data throughput to the output interface. As shown in the figure below,
15 | the core of the control path design of the ring arbiter consists of two :ref:`rotate` blocks, a
16 | :ref:`one-hot-priority-encoder`, and a rotate controller. The pre-:ref:`rotate` +
17 | :ref:`one-hot-priority-encoder` + post-:ref:`rotate` combination determines which input port will be serviced
18 | next while the rotate controller calculates the rotation amounts such that the last accepted input port is
19 | the lowest priority input port until a new transaction is accepted from a different input port, thus changing
20 | the last accepted input port. Therefore, the prev_accepted register is only written when the
21 | :ref:`skid-buffer` indicates that it has accepted a new transaction from an input port.
22 |
23 | The datapath for the ring arbiter consists of a :ref:`one-hot-mux` and a :ref:`skid-buffer`. The
24 | :ref:`one-hot-mux` selects the data from the input port that has been decided by the control path so that the
25 | :ref:`skid-buffer` sees the correct input port's data at its input. On the output, the :ref:`skid-buffer`'s
26 | output is connected directly to the output interface. The :ref:`skid-buffer` is used here because it provides
27 | the back-to-back transfers needed on a pipelined interface in a fairly lightweight manner.
28 |
29 | Note that the ``i_clock``, ``i_aresetn``, and ``i_clear`` signals are intentionally omitted from the block diagram
30 | below to make it easier to read and understand the ring arbiter design.
31 |
32 | .. image:: _static/ring_arbiter.svg
33 | :align: center
34 | :width: 80%
35 | :alt: Ring Arbiter Block Diagram
36 |
37 | Parameters
38 | ----------
39 | - ``PORTS`` : number of input ports each of ``DATA_WIDTH`` bits
40 | - ``DATA_WIDTH`` : data width per input port in bits
41 |
42 | Ports
43 | -----
44 | - ``i_clock`` : input clock
45 | - ``i_aresetn`` : asynchronous active-low reset
46 | - ``i_clear`` : synchronous clear
47 | - ``i_data`` : input data of all ports concatenated into a single vector (size is ``PORTS*DATA_WIDTH`` bits)
48 | - ``i_input_valid`` : valid signals from all input ports. Used with o_input_ready for input handshaking
49 | - ``i_output_ready`` : ready signal from output interface. Used with o_output_valid for output handshaking
50 | - ``o_data`` : output data (size is ``DATA_WIDTH`` bits)
51 | - ``o_output_valid`` : valid signal from output interface. Used with i_output_ready for output handshaking
52 | - ``o_input_ready`` : ready signals to all input ports. Used with i_input_valid for input handshaking
53 | - ``o_accept`` : accept status signal that indicates whenever input data is accepted by the ring arbiter
54 | - ``o_transmit`` : transmit status signal that indicates whenever output data is transmitted by the ring arbiter
55 |
56 | Source Code
57 | -----------
58 | .. literalinclude:: ../../libsv/arbiters/ring_arbiter.sv
59 | :language: systemverilog
60 | :linenos:
61 | :caption: ring_arbiter.sv
62 |
--------------------------------------------------------------------------------
/libsv/fifos/skid_buffer.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_FIFOS_SKID_BUFFER
2 | `define LIBSV_FIFOS_SKID_BUFFER
3 |
4 | module skid_buffer #(
5 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 32
6 | ) (
7 | input logic i_clock,
8 | input logic i_aresetn,
9 | input logic i_clear,
10 | input logic [DATA_WIDTH-1:0] i_data,
11 | input logic i_input_valid,
12 | input logic i_output_ready,
13 | output logic [DATA_WIDTH-1:0] o_data,
14 | output logic o_output_valid,
15 | output logic o_input_ready,
16 | output logic o_accept,
17 | output logic o_transmit
18 | );
19 |
20 | // CONTROL PATH -----------------------------
21 |
22 | typedef enum logic [2:0] {
23 | EMPTY = 3'b001,
24 | BUSY = 3'b010,
25 | FULL = 3'b100
26 | } state_t;
27 |
28 | state_t state, next_state; // state variables
29 | logic accept, transmit; // handshake flags on each interface
30 | logic [DATA_WIDTH-1:0] buffer; // the "skid" buffer
31 |
32 | assign o_accept = accept;
33 | assign o_transmit = transmit;
34 |
35 | always_comb begin : next_state_logic
36 | accept = i_input_valid && o_input_ready; // check for input handshake
37 | transmit = o_output_valid && i_output_ready; // check for output handshake
38 | next_state = EMPTY;
39 | unique case (state)
40 | EMPTY: begin
41 | next_state = EMPTY;
42 | if (accept) next_state = BUSY;
43 | end
44 | BUSY: begin
45 | next_state = BUSY;
46 | if (accept && !transmit) next_state = FULL;
47 | else if (!accept && transmit) next_state = EMPTY;
48 | end
49 | FULL: begin
50 | next_state = FULL;
51 | if (transmit) next_state = BUSY;
52 | end
53 | default: next_state = EMPTY;
54 | endcase
55 | end : next_state_logic
56 |
57 | always_ff @(posedge i_clock, negedge i_aresetn) begin : update_state_logic
58 | if (!i_aresetn || i_clear) begin
59 | state <= EMPTY;
60 | o_input_ready <= 1'b0;
61 | o_output_valid <= 1'b0;
62 | end else begin
63 | state <= next_state;
64 | o_input_ready <= next_state != FULL;
65 | o_output_valid <= next_state != EMPTY;
66 | end
67 | end : update_state_logic
68 |
69 | logic buffer_write_en, o_data_write_en;
70 | always_comb begin : write_en_logic
71 | buffer_write_en = state == BUSY && accept && !transmit;
72 | o_data_write_en = (state == EMPTY && accept && !transmit)
73 | || (state == BUSY && accept && transmit)
74 | || (state == FULL && !accept && transmit);
75 | end : write_en_logic
76 |
77 | // END OF CONTROL PATH ----------------------
78 |
79 | // DATA PATH --------------------------------
80 |
81 | always_ff @(posedge i_clock, negedge i_aresetn) begin : o_data_and_buffer_logic
82 | if (!i_aresetn || i_clear) begin
83 | o_data <= '0;
84 | buffer <= '0;
85 | end else begin
86 |
87 | if (o_data_write_en) begin
88 | if (state == FULL) o_data <= buffer;
89 | else o_data <= i_data;
90 | end
91 |
92 | if (buffer_write_en) begin
93 | buffer <= i_data;
94 | end
95 |
96 | end
97 | end : o_data_and_buffer_logic
98 |
99 | // END OF DATA PATH -------------------------
100 |
101 | endmodule
102 |
103 | `endif /* LIBSV_FIFOS_SKID_BUFFER */
104 |
--------------------------------------------------------------------------------
/libsv/arbiters/ring_arbiter.sv:
--------------------------------------------------------------------------------
1 | `ifndef LIBSV_ARBITERS_RING_ARBITER
2 | `define LIBSV_ARBITERS_RING_ARBITER
3 |
4 | `include "libsv/bit_ops/rotate.sv"
5 | `include "libsv/coders/onehot_priority_encoder.sv"
6 | `include "libsv/muxes/onehot_mux.sv"
7 | `include "libsv/fifos/skid_buffer.sv"
8 |
9 | module ring_arbiter #(
10 | parameter int PORTS /* verilator public_flat_rd */ = 4,
11 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 8
12 | ) (
13 | input logic i_clock,
14 | input logic i_aresetn,
15 | input logic i_clear,
16 | // This is a bummer. Would much rather do:
17 | // input logic [PORTS-1:0][DATA_WIDTH-1:0] i_data
18 | // and have a 2D packed array but verilator doesn't
19 | // support 2D packed arrays using VPI which is what
20 | // cocotb uses. See https://github.com/verilator/verilator/issues/2812.
21 | input logic [PORTS*DATA_WIDTH-1:0] i_data,
22 | input logic [ PORTS-1:0] i_input_valid,
23 | input logic i_output_ready,
24 | output logic [ DATA_WIDTH-1:0] o_data,
25 | output logic o_output_valid,
26 | output logic [ PORTS-1:0] o_input_ready,
27 | output logic o_accept,
28 | output logic o_transmit
29 | );
30 |
31 | logic [$clog2(PORTS)-1:0] pre_rotate_amt;
32 | logic [ PORTS-1:0] pre_rotate_out;
33 | logic [ PORTS-1:0] ohpe_out;
34 | logic ohpe_valid;
35 | logic [$clog2(PORTS)-1:0] post_rotate_amt;
36 | logic [ PORTS-1:0] post_rotate_out;
37 | logic [ PORTS-1:0] prev_accepted;
38 | logic accept, transmit;
39 | logic is_any_input_valid;
40 | logic input_ready;
41 | logic [DATA_WIDTH-1:0] ohm_out;
42 |
43 | // CONTROL PATH -------------------------------
44 |
45 | // pre-rotate
46 | rotate #(PORTS) pre_rotate (
47 | .i_data(i_input_valid),
48 | .i_amt (pre_rotate_amt),
49 | .o_data(pre_rotate_out)
50 | );
51 |
52 | // onehot priority encoder selects the current highest priority input port
53 | onehot_priority_encoder #(PORTS) ohpe (
54 | .i_data(pre_rotate_out),
55 | .o_data(ohpe_out)
56 | );
57 |
58 | // post-rotate (undo the pre-rotate)
59 | rotate #(PORTS) post_rotate (
60 | .i_data(ohpe_out),
61 | .i_amt (post_rotate_amt),
62 | .o_data(post_rotate_out)
63 | );
64 |
65 | always_ff @(posedge i_clock, negedge i_aresetn) begin : prev_accepted_logic
66 | if (!i_aresetn || i_clear) begin
67 | prev_accepted <= '0;
68 | end else begin
69 | if (accept) prev_accepted <= post_rotate_out;
70 | else prev_accepted <= prev_accepted;
71 | end
72 | end : prev_accepted_logic
73 |
74 | always_comb begin : rotate_controller
75 | pre_rotate_amt = '0;
76 | post_rotate_amt = '0;
77 | for (int i = 0; i < $bits(prev_accepted); ++i) begin
78 | if (prev_accepted[i]) begin
79 | pre_rotate_amt = $bits(pre_rotate_amt)'(PORTS - i - 1);
80 | post_rotate_amt = $bits(post_rotate_amt)'(i + 1);
81 | end
82 | end
83 | end : rotate_controller
84 |
85 | assign is_any_input_valid = |i_input_valid;
86 | assign o_input_ready = {PORTS{input_ready}} & post_rotate_out;
87 | assign o_accept = accept;
88 | assign o_transmit = transmit;
89 |
90 | // END OF CONTROL PATH ------------------------
91 |
92 | // DATA PATH ----------------------------------
93 |
94 | onehot_mux #(
95 | .PORTS (PORTS),
96 | .DATA_WIDTH(DATA_WIDTH)
97 | ) ohm (
98 | .i_data (i_data),
99 | .i_select(post_rotate_out),
100 | .o_data (ohm_out)
101 | );
102 |
103 | skid_buffer #(DATA_WIDTH) sb (
104 | .i_clock (i_clock),
105 | .i_aresetn (i_aresetn),
106 | .i_clear (i_clear),
107 | .i_data (ohm_out),
108 | .i_input_valid (is_any_input_valid),
109 | .i_output_ready(i_output_ready),
110 | .o_data (o_data),
111 | .o_output_valid(o_output_valid),
112 | .o_input_ready (input_ready),
113 | .o_accept (accept),
114 | .o_transmit (transmit)
115 | );
116 |
117 | // END OF DATA PATH ---------------------------
118 |
119 | endmodule : ring_arbiter
120 |
121 | `endif /* LIBSV_ARBITERS_RING_ARBITER */
122 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on:
4 | push:
5 | branches:
6 | - "**"
7 | tags:
8 | - "v*"
9 |
10 | jobs:
11 | # This job builds and updates the docker images on the LibSV's Docker Hub. It will create and push a
12 | # new image if any of the following conditions apply:
13 | # 1. This is a new commit to the 'main' branch.
14 | # 2. If a tag for this branch does not already exist on LibSV's Docker Hub.
15 | # 3. If the Dockerfile in the top project directory changed in the most recent commit.
16 | docker:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v2
21 | with:
22 | fetch-depth: 2
23 |
24 | - name: Check if Dockerfile changed in last commit
25 | run: |
26 | echo "DOCKERFILE_CHANGED=$(git diff --name-only HEAD~1 HEAD | grep -c Dockerfile)" >> $GITHUB_ENV
27 |
28 | - name: Docker meta
29 | id: docker_meta
30 | uses: docker/metadata-action@v3
31 | with:
32 | images: bensampson5/libsv
33 |
34 | - name: Check if docker image already exists
35 | run: |
36 | echo '{"experimental": "enabled"}' > ~/.docker/config.json;
37 | if [[ $(docker manifest inspect ${{ steps.docker_meta.outputs.tags }} 2>/dev/null) ]]; then
38 | echo "DOCKER_IMAGE_EXISTS=1" >> $GITHUB_ENV;
39 | else
40 | echo "DOCKER_IMAGE_EXISTS=0" >> $GITHUB_ENV;
41 | fi;
42 |
43 | - name: Set up QEMU
44 | if: env.DOCKERFILE_CHANGED == '1' || env.DOCKER_IMAGE_EXISTS == '0' || github.ref == 'refs/heads/main'
45 | uses: docker/setup-qemu-action@v1
46 |
47 | - name: Set up Docker Buildx
48 | if: env.DOCKERFILE_CHANGED == '1' || env.DOCKER_IMAGE_EXISTS == '0' || github.ref == 'refs/heads/main'
49 | uses: docker/setup-buildx-action@v1
50 |
51 | - name: Cache Docker layers
52 | if: env.DOCKERFILE_CHANGED == '1' || env.DOCKER_IMAGE_EXISTS == '0' || github.ref == 'refs/heads/main'
53 | uses: actions/cache@v2
54 | with:
55 | path: /tmp/.buildx-cache
56 | key: ${{ runner.os }}-buildx-${{ github.sha }}
57 | restore-keys: |
58 | ${{ runner.os }}-buildx-
59 |
60 | - name: Login to Docker Hub
61 | if: github.event_name != 'pull_request' && (env.DOCKERFILE_CHANGED == '1' || env.DOCKER_IMAGE_EXISTS == '0' || github.ref == 'refs/heads/main')
62 | uses: docker/login-action@v1
63 | with:
64 | username: ${{ secrets.DOCKERHUB_USERNAME }}
65 | password: ${{ secrets.DOCKERHUB_TOKEN }}
66 |
67 | - name: Build and push
68 | if: env.DOCKERFILE_CHANGED == '1' || env.DOCKER_IMAGE_EXISTS == '0' || github.ref == 'refs/heads/main'
69 | id: docker_build
70 | uses: docker/build-push-action@v2
71 | with:
72 | context: .
73 | file: ./Dockerfile
74 | platforms: linux/amd64
75 | push: ${{ github.event_name != 'pull_request' }}
76 | tags: ${{ steps.docker_meta.outputs.tags }}
77 | labels: ${{ steps.docker_meta.outputs.labels }}
78 | cache-from: type=local,src=/tmp/.buildx-cache
79 | cache-to: type=local,dest=/tmp/.buildx-cache
80 |
81 | - name: Image digest
82 | if: env.DOCKERFILE_CHANGED == '1' || env.DOCKER_IMAGE_EXISTS == '0' || github.ref == 'refs/heads/main'
83 | run: echo ${{ steps.docker_build.outputs.digest }}
84 |
85 | # This job runs all the LibSV builds, tests, and checks
86 | libsv:
87 | needs: [docker]
88 | runs-on: ubuntu-latest
89 | steps:
90 | - name: Checkout
91 | uses: actions/checkout@v2
92 |
93 | - name: Docker meta
94 | id: docker_meta
95 | uses: docker/metadata-action@v3
96 | with:
97 | images: bensampson5/libsv
98 |
99 | - name: Setup GitHub environment
100 | run: echo "DOCKER_RUN=docker run --rm -v $(pwd):/code bensampson5/libsv:${{ steps.docker_meta.outputs.version }}" >> $GITHUB_ENV;
101 |
102 | - name: Pull docker image
103 | run: docker pull bensampson5/libsv:${{ steps.docker_meta.outputs.version }}
104 |
105 | - name: Build and run tests
106 | run: ${{ env.DOCKER_RUN }} /code/tools/precommit.py --test
107 |
108 | - name: Check format
109 | run: ${{ env.DOCKER_RUN }} /code/tools/precommit.py --check-format
110 |
111 | - name: Lint
112 | run: ${{ env.DOCKER_RUN }} /code/tools/precommit.py --lint
113 |
114 | - name: Build docs
115 | run: ${{ env.DOCKER_RUN }} /code/tools/precommit.py --docs
116 |
117 | - name: Build package
118 | run: ${{ env.DOCKER_RUN }} /code/tools/precommit.py --build-package
119 |
120 | - name: Publish package
121 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
122 | uses: pypa/gh-action-pypi-publish@release/v1
123 | with:
124 | user: __token__
125 | password: ${{ secrets.PYPI_API_TOKEN }}
126 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | .. image:: _static/libsv_logo.svg
2 | :align: center
3 | :height: 200
4 | :alt: LibSV
5 |
6 | ------------------------------------------------------------------------------------------------------------------------
7 |
8 | Welcome to LibSV's documentation!
9 | =================================
10 |
11 | LibSV is an open source, parameterized SystemVerilog digital hardware IP library.
12 | While similar libraries may already exist, LibSV is unique in that it takes advantage
13 | of open-source, state-of-the-art development best practices and tools from across the
14 | software and digital design community, including:
15 |
16 | * Trivial installation. `LibSV is hosted on PyPI `_ and can easily be installed using
17 | `pip `_ or whichever Python package manager of your choice.
18 | * Easy-to-use. Simply add ```include "libsv///.sv"`` to where you want to use a LibSV module and then add the
19 | ``site-packages/`` folder, where LibSV was installed, to the include path when building your project.
20 | * Automated testbenches, written in Python, that use `pytest `_ to run
21 | `Cocotb `_ + `Verilator `_ under the hood for
22 | simple and fast logic simulation
23 | * All testbenches output waveform files in FST format for viewing with `GTKWave `_
24 | * `Extensive documention `_ using `Sphinx `_
25 | * Automated formatting and lint checks using `Verible `_
26 | * `Continuous integration (CI) workflows `_ integrated with
27 | `Docker `_
28 | * `LibSV Docker images `_ published to
29 | `Docker Hub `_
30 |
31 | Getting Started
32 | ---------------
33 |
34 | LibSV is very easy to use. First, install the ``libsv`` package from PyPI:
35 |
36 | .. code-block:: bash
37 |
38 | pip install libsv
39 |
40 | We recommend using a Python virtual environment so that the installation is project-specific and
41 | isolated from the rest of your system.
42 |
43 | Then add the ``site-packages/`` folder, where LibSV was just installed, to your include path when building your
44 | project so that your design tools can find LibSV.
45 |
46 | Finally, at the top of your design file where you want to use LibSV modules, for each module you want to use, add:
47 |
48 | .. code-block:: SystemVerilog
49 |
50 | `include "libsv///.sv"
51 |
52 | Running Testbenches
53 | -------------------
54 |
55 | Running the LibSV testbenches require `Cocotb `_,
56 | `Verilator `_, and a number of other dependencies to be installed.
57 | Instead of trying to install everything manually on your machine, the easier and recommended way to run the
58 | LibSV testbenches is to use the pre-built
59 | `LibSV Docker images on Docker Hub `__ that have the
60 | complete set of LibSV developer tools already installed.
61 |
62 | To use a LibSV Docker image, first you’ll need to install `Docker `__,
63 | if you don’t already have it.
64 |
65 | Next, pull the latest LibSV Docker image:
66 |
67 | .. code-block:: bash
68 |
69 | docker build --pull -f Dockerfile.dev \
70 | --build-arg UID=$(id -u) \
71 | --build-arg GID=$(id -g) \
72 | -t libsv .
73 |
74 | Then, start a new Docker container using the LibSV image and mount the project folder to the container:
75 |
76 | .. code-block:: bash
77 |
78 | docker run --rm -it -v $(pwd):/code libsv
79 |
80 | Finally, within the Docker container, run ``pytest``:
81 |
82 | .. code-block:: bash
83 |
84 | pytest
85 |
86 | This will run all the LibSV testbenches for the entire library (*Warning: This may take a while!*).
87 |
88 | Instead, to list all the available LibSV testbenches, run:
89 |
90 | .. code-block:: bash
91 |
92 | pytest --co
93 |
94 | Then, you can run an individual or subset of testbenches using the ``-k`` flag which will only run tests which
95 | match the given substring expression:
96 |
97 | .. code-block:: bash
98 |
99 | pytest -k EXPRESSION
100 |
101 | Each testbench generates an associated ``.fst`` waveform file that is written to the ``build/`` directory and can be
102 | viewed using `GTKWave `_.
103 |
104 | Bugs/Feature Requests
105 | ---------------------
106 |
107 | Please use `LibSV's GitHub issue tracker `_ to submit bugs or request features.
108 |
109 | Contributing
110 | ------------
111 |
112 | Contributions are much welcomed and appreciated! Take a look at the :doc:`contributing` page to get started.
113 |
114 | License
115 | -------
116 |
117 | Distributed under the terms of the `MIT `_ license, LibSV is free
118 | and open source software.
119 |
120 | .. toctree::
121 | :maxdepth: 1
122 | :caption: Table of Contents:
123 |
124 | modules
125 | contributing
126 | license
127 | useful_links
128 | contact
129 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://raw.githubusercontent.com/bensampson5/libsv/main/docs/source/_static/libsv_logo.svg
2 | :align: center
3 | :height: 150
4 | :alt: LibSV
5 |
6 | ------------------------------------------------------------------------------------------------------------------------
7 |
8 | .. image:: https://img.shields.io/pypi/v/libsv
9 | :target: https://pypi.org/project/libsv/
10 | :alt: PyPI
11 |
12 | .. image:: https://github.com/bensampson5/libsv/actions/workflows/ci.yml/badge.svg
13 | :target: https://github.com/bensampson5/libsv/actions/workflows/ci.yml
14 |
15 | .. image:: https://readthedocs.org/projects/libsv/badge/?version=latest
16 | :target: https://libsv.readthedocs.io/en/latest/?badge=latest
17 | :alt: Documentation Status
18 |
19 | Welcome to LibSV! `Click here to go to LibSV’s
20 | documentation `_.
21 |
22 | LibSV is an open source, parameterized SystemVerilog digital hardware IP library.
23 | While similar libraries may already exist, LibSV is unique in that it takes advantage
24 | of open-source, state-of-the-art development best practices and tools from across the
25 | software and digital design community, including:
26 |
27 | * Trivial installation. `LibSV is hosted on PyPI `_ and can easily be installed using
28 | `pip `_ or whichever Python package manager of your choice.
29 | * Easy-to-use. Simply add ```include "libsv///.sv"`` to where you want to use a LibSV module and then add the
30 | ``site-packages/`` folder, where LibSV was installed, to the include path when building your project.
31 | * Automated testbenches, written in Python, that use `pytest `_ to run
32 | `Cocotb `_ + `Verilator `_ under the hood for
33 | simple and fast logic simulation
34 | * All testbenches output waveform files in FST format for viewing with `GTKWave `_
35 | * `Extensive documention `_ using `Sphinx `_
36 | * Automated formatting and lint checks using `Verible `_
37 | * `Continuous integration (CI) workflows `_ integrated with
38 | `Docker `_
39 | * `LibSV Docker images `_ published to
40 | `Docker Hub `_
41 |
42 | Getting Started
43 | ---------------
44 |
45 | LibSV is very easy to use. First, install the ``libsv`` package from PyPI:
46 |
47 | .. code-block:: bash
48 |
49 | pip install libsv
50 |
51 | We recommend using a Python virtual environment so that the installation is project-specific and
52 | isolated from the rest of your system.
53 |
54 | Then add the ``site-packages/`` folder, where LibSV was just installed, to your include path when building your
55 | project so that your design tools can find LibSV.
56 |
57 | Finally, at the top of your design file where you want to use LibSV modules, for each module you want to use, add:
58 |
59 | .. code-block:: SystemVerilog
60 |
61 | `include "libsv///.sv"
62 |
63 | Running Testbenches
64 | -------------------
65 |
66 | Running the LibSV testbenches require `Cocotb `_,
67 | `Verilator `_, and a number of other dependencies to be installed.
68 | Instead of trying to install everything manually on your machine, the easier and recommended way to run the
69 | LibSV testbenches is to use the pre-built
70 | `LibSV Docker images on Docker Hub `__ that have the
71 | complete set of LibSV developer tools already installed.
72 |
73 | To use a LibSV Docker image, first you’ll need to install `Docker `__,
74 | if you don’t already have it.
75 |
76 | Next, pull the latest LibSV Docker image:
77 |
78 | .. code-block:: bash
79 |
80 | docker build --pull -f Dockerfile.dev \
81 | --build-arg UID=$(id -u) \
82 | --build-arg GID=$(id -g) \
83 | -t libsv .
84 |
85 | Then, start a new Docker container using the LibSV image and mount the project folder to the container:
86 |
87 | .. code-block:: bash
88 |
89 | docker run --rm -it -v $(pwd):/code libsv
90 |
91 | Finally, within the Docker container, run ``pytest``:
92 |
93 | .. code-block:: bash
94 |
95 | pytest
96 |
97 | This will run all the LibSV testbenches for the entire library (*Warning: This may take a while!*).
98 |
99 | Instead, to list all the available LibSV testbenches, run:
100 |
101 | .. code-block:: bash
102 |
103 | pytest --co
104 |
105 | Then, you can run an individual or subset of testbenches using the ``-k`` flag which will only run tests which
106 | match the given substring expression:
107 |
108 | .. code-block:: bash
109 |
110 | pytest -k EXPRESSION
111 |
112 | Each testbench generates an associated ``.fst`` waveform file that is written to the ``build/`` directory and can be
113 | viewed using `GTKWave `_.
114 |
115 | Bugs/Feature Requests
116 | ---------------------
117 |
118 | Please use `LibSV's GitHub issue tracker `_ to submit bugs or request features.
119 |
120 | Contributing
121 | ------------
122 |
123 | Contributions are much welcomed and appreciated! Take a look at the
124 | `Contributing `_ page to get started.
125 |
126 | License
127 | -------
128 |
129 | Distributed under the terms of the `MIT `_ license, LibSV is free
130 | and open source software.
131 |
--------------------------------------------------------------------------------
/.verible-verilog-format.yaml:
--------------------------------------------------------------------------------
1 | # Yaml configuration file for verible-verilog-format.
2 | # Generated from `verible-verilog-format --helpfull` output and
3 | # then formatted into a yaml file for convenience.
4 |
5 | # assignment_statement_alignment - Format various assignments: {align,flush-left,preserve,infer};
6 | # default: infer;
7 | assignment_statement_alignment: align
8 |
9 | # case_items_alignment - Format case items: {align,flush-left,preserve,infer}; default: infer;
10 | case_items_alignment: align
11 |
12 | # class_member_variables_alignment - Format class member variables:
13 | # {align,flush-left,preserve,infer}; default: infer;
14 | class_member_variable_alignment: align
15 |
16 | # column_limit (Target line length limit to stay under when formatting.); default: 100;
17 | column_limit: 100
18 |
19 | # compact_indexing_and_selections - Use compact binary expressions inside indexing / bit selection
20 | # operators; default: true;
21 | compact_indexing_and_selections: true
22 |
23 | # distribution_items_alignment Align distribution items: {align,flush-left,preserve,infer});
24 | # default: infer;
25 | distribution_items_alignment: align
26 |
27 | # enum_assignment_statement_alignment (Format assignments with enums: {align,flush-left,preserve,infer});
28 | # default: infer;
29 | enum_assignment_statement_alignment: align
30 |
31 | # expand_coverpoints (If true, always expand coverpoints.); default: false;
32 | expand_coverpoints: false
33 |
34 | # failsafe_success - If true, always exit with 0 status, even if there were input errors or
35 | # internal errors. In all error conditions, the original text is always preserved. This is
36 | # useful in deploying services where fail-safe behaviors should be considered a success.;
37 | # default: true;
38 | failsafe_success: true
39 |
40 | # formal_parameters_alignment - Format formal parameters: {align,flush-left,preserve,infer};
41 | # default: infer;
42 | formal_parameters_alignment: align
43 |
44 | # formal_parameters_indentation - Indent formal parameters: {indent,wrap}; default: wrap;
45 | formal_parameters_indentation: wrap
46 |
47 | # indentation_spaces (Each indentation level adds this many spaces.); default: 2;
48 | indentation_spaces: 4
49 |
50 | # line_break_penalty (Penalty added to solution for each introduced line break.); default: 2;
51 | line_break_penalty: 2
52 |
53 | # max_search_states - Limits the number of search states explored during line wrap
54 | # optimization.; default: 100000;
55 | max_search_states: 100000
56 |
57 | # module_net_variable_alignment (Format net/variable declarations: {align,flush-left,preserve,infer});
58 | # default: infer;
59 | module_net_variable_alignment: align
60 |
61 | # named_parameter_alignment - Format named actual parameters: {align,flush-left,preserve,infer};
62 | # default: infer;
63 | named_parameter_alignment: align
64 |
65 | # named_parameter_indentation - Indent named parameter assignments: {indent,wrap};
66 | # default: wrap;
67 | named_parameter_indentation: wrap
68 |
69 | # named_port_alignment - Format named port connections: {align,flush-left,preserve,infer};
70 | # default: infer;
71 | named_port_alignment: align
72 |
73 | # named_port_indentation - Indent named port connections: {indent,wrap}; default: wrap;
74 | named_port_indentation: wrap
75 |
76 | # over_column_limit_penalty (For penalty minimization, this represents the baseline penalty value
77 | # of exceeding the column limit. Additional penalty of 1 is incurred for each character over this
78 | # limit); default: 100;
79 | over_column_limit_penalty: 100
80 |
81 | # port_declarations_alignment - Format port declarations: {align,flush-left,preserve,infer};
82 | # default: infer;
83 | port_declarations_alignment: align
84 |
85 | # port_declarations_indentation - Indent port declarations: {indent,wrap}; default: wrap;
86 | port_declarations_indentation: wrap
87 |
88 | # port_declarations_right_align_packed_dimensions (If true, packed dimensions in contexts
89 | # with enabled alignment are aligned to the right.); default: false;
90 | port_declarations_right_align_packed_dimensions: false
91 |
92 | # port_declarations_right_align_unpacked_dimensions (If true, unpacked dimensions in contexts
93 | # with enabled alignment are aligned to the right.); default: false;
94 | port_declarations_right_align_unpacked_dimensions: false
95 |
96 | # show_equally_optimal_wrappings - If true, print when multiple optimal solutions are found
97 | # (stderr), but continue to operate normally.; default: false;
98 | show_equally_optimal_wrappings: false
99 |
100 | # show_inter_token_info - If true, along with show_token_partition_tree, include inter-token
101 | # information such as spacing and break penalties.; default: false;
102 | show_inter_token_info: false
103 |
104 | # show_largest_token_partitions - If > 0, print token partitioning and then exit without
105 | # formatting output.; default: 0;
106 | show_largest_token_partitions: 0
107 |
108 | # show_token_partition_tree - If true, print diagnostics after token partitioning and then
109 | # exit without formatting output.; default: false;
110 | show_token_partition_tree: false
111 |
112 | # struct_union_members_alignment (Format struct/union members: {align,flush-left,preserve,infer});
113 | # default: infer;
114 | struct_union_members_alignment: align
115 |
116 | # try_wrap_long_lines - If true, let the formatter attempt to optimize line wrapping decisions
117 | # where wrapping is needed, else leave them unformatted. This is a short-term measure to reduce
118 | # risk-of-harm.; default: false;
119 | try_wrap_long_lines: false
120 |
121 | # verify_convergence - If true, and not incrementally formatting with --lines, verify that
122 | # re-formatting the formatted output yields no further changes, i.e. formatting is convergent.;
123 | # default: true;
124 | verify_convergence: true
125 |
126 | # wrap_spaces (Each wrap level adds this many spaces. This applies when the first element after
127 | # an open-group section is wrapped. Otherwise, the indentation level is set to the column position
128 | # of the open-group operator.); default: 4;
129 | wrap_spaces: 4
--------------------------------------------------------------------------------
/docs/source/_static/libsv_logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
197 |
--------------------------------------------------------------------------------
/tools/precommit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import click
4 | from pathlib import Path
5 | import shutil
6 | import subprocess
7 | import yaml
8 | from difflib import unified_diff
9 | import colorama
10 | from colorama import Fore, Style
11 |
12 | PROJECT_ROOT = Path("/code")
13 | SRC_DIR = PROJECT_ROOT / "libsv"
14 | DOCS_DIR = PROJECT_ROOT / "docs"
15 | FLUSH = True
16 |
17 |
18 | def in_docker():
19 | """Returns: True if running in a docker container, else False"""
20 | return Path("/.dockerenv").exists()
21 |
22 |
23 | def run(cmd, cwd=PROJECT_ROOT, check_exit=True, print_output=True):
24 | p = subprocess.Popen(
25 | cmd,
26 | cwd=cwd,
27 | stdout=subprocess.PIPE,
28 | stderr=subprocess.STDOUT,
29 | universal_newlines=True,
30 | )
31 |
32 | while True:
33 | stdout = p.stdout.readline()
34 | if stdout == "" and p.poll() is not None:
35 | break
36 | if stdout:
37 | if print_output:
38 | print(stdout, end="", flush=FLUSH)
39 |
40 | if check_exit and p.returncode != 0:
41 | raise subprocess.CalledProcessError(p.returncode, " ".join(cmd))
42 |
43 |
44 | def run_test():
45 | """Run tests"""
46 | cmd = ["pytest"]
47 | run(cmd)
48 |
49 |
50 | def find_sv_files(dir=SRC_DIR):
51 | # Add all SystemVerilog files within project
52 | hdl_search_patterns = ["**/*.sv"]
53 | hdl_files = []
54 | for sp in hdl_search_patterns:
55 | hdl_files += dir.glob(sp)
56 | return hdl_files
57 |
58 |
59 | def run_check_format():
60 | """Check formatting in all files"""
61 | run_check_format_hdl()
62 | run_check_format_python()
63 |
64 |
65 | def run_check_format_hdl():
66 | """Check formatting in HDL files"""
67 |
68 | print("\nChecking HDL formatting...\n", flush=FLUSH)
69 |
70 | hdl_files = find_sv_files()
71 |
72 | # Make a copy of original HDL code
73 | hdl_file_code_original = []
74 | for hdl_file in hdl_files:
75 | with open(hdl_file, "r") as f:
76 | hdl_file_code_original.append(f.readlines())
77 |
78 | # Run hdl formatter
79 | run_fix_format_hdl(print_output=False)
80 |
81 | # Make a copy of the formatted HDL code
82 | hdl_file_code_formatted = []
83 | for hdl_file in hdl_files:
84 | with open(hdl_file, "r") as f:
85 | hdl_file_code_formatted.append(f.readlines())
86 |
87 | # Do a diff and print diff output in pretty colors
88 | colorama.init()
89 | is_diff = False
90 | for i in range(len(hdl_file_code_original)):
91 | original = hdl_file_code_original[i]
92 | formatted = hdl_file_code_formatted[i]
93 | fname = str(hdl_files[i])
94 | diff = list(
95 | unified_diff(original, formatted, fromfile=fname, tofile=fname, n=5)
96 | )
97 | if diff:
98 | is_diff = True
99 | print_unified_diff_in_color(diff)
100 | colorama.deinit()
101 |
102 | # Restore original HDL code
103 | for i in range(len(hdl_files)):
104 | with open(hdl_files[i], "w") as f:
105 | f.write("".join(hdl_file_code_original[i]))
106 |
107 | if is_diff:
108 | raise RuntimeError("HDL format check failed")
109 |
110 |
111 | def print_unified_diff_in_color(diff):
112 | for line in diff:
113 | if line.startswith("---") or line.startswith("+++"):
114 | line = f"{Style.BRIGHT}{line}{Style.RESET_ALL}"
115 | elif line.startswith("@@"):
116 | line = f"{Fore.CYAN}{line}{Style.RESET_ALL}"
117 | elif line.startswith("-"):
118 | line = f"{Fore.RED}{line}{Style.RESET_ALL}"
119 | elif line.startswith("+"):
120 | line = f"{Fore.GREEN}{line}{Style.RESET_ALL}"
121 | print(line, end="", flush=FLUSH)
122 | print()
123 |
124 |
125 | def run_check_format_python():
126 | """Check formatting in Python files"""
127 | print("\nChecking Python formatting...\n", flush=FLUSH)
128 | cmd = ["black", "--diff", "--check", "--color", "."]
129 | run(cmd)
130 |
131 |
132 | def run_fix_format():
133 | """Fix formatting in all files"""
134 | print("\nFixing HDL formatting...\n", flush=FLUSH)
135 | run_fix_format_hdl()
136 | run_fix_format_python()
137 |
138 |
139 | def run_fix_format_hdl(print_output=True):
140 | """Fix formatting in HDL files"""
141 |
142 | # Use --inplace flag to overwrite existing files
143 | cmd = ["verible-verilog-format", "--inplace"]
144 |
145 | # Add options from .verible-verilog-format.yaml if specified
146 | verible_verilog_format_yaml = PROJECT_ROOT / ".verible-verilog-format.yaml"
147 | yaml_data = None
148 | if verible_verilog_format_yaml.exists():
149 | with open(verible_verilog_format_yaml, "r") as f:
150 | yaml_data = yaml.safe_load(f.read())
151 |
152 | format_args = []
153 | for k, v in yaml_data.items():
154 | format_args.append(f"--{k}={v}")
155 |
156 | cmd += format_args
157 |
158 | hdl_files = find_sv_files()
159 | cmd += [str(f) for f in hdl_files]
160 |
161 | run(cmd, print_output=print_output)
162 |
163 |
164 | def run_fix_format_python():
165 | """Fix formatting in Python files"""
166 |
167 | print("\nFixing Python formatting...\n", flush=FLUSH)
168 | cmd = ["black", "."]
169 | run(cmd)
170 |
171 |
172 | def run_lint():
173 | run_lint_hdl()
174 | run_lint_python()
175 |
176 |
177 | def run_lint_hdl():
178 | """Run HDL linter"""
179 |
180 | print("\nLinting HDL...\n", flush=FLUSH)
181 |
182 | cmd = ["verible-verilog-lint"]
183 |
184 | hdl_files = find_sv_files()
185 | cmd += [str(f) for f in hdl_files]
186 |
187 | run(cmd)
188 |
189 |
190 | def run_lint_python():
191 | """Run Python linter"""
192 |
193 | print("\nLinting Python...\n", flush=FLUSH)
194 | cmd = ["flake8", "."]
195 | run(cmd)
196 |
197 |
198 | def run_docs():
199 | """Make documentation"""
200 |
201 | DOCS_BUILD_DIR = DOCS_DIR / "build"
202 |
203 | # Delete entire docs build directory if it exists
204 | if DOCS_BUILD_DIR.exists():
205 | shutil.rmtree(DOCS_BUILD_DIR)
206 |
207 | # Create new docs build directory
208 | DOCS_BUILD_DIR.mkdir()
209 |
210 | cmd = ["make", "html"]
211 | run(cmd, cwd=DOCS_DIR)
212 |
213 |
214 | def run_build_package():
215 | """Builds PyPI package"""
216 |
217 | cmd = ["poetry", "build"]
218 | run(cmd)
219 |
220 |
221 | @click.command()
222 | @click.option("--test", is_flag=True, help="Run tests")
223 | @click.option("--check-format", is_flag=True, help="Check formatting")
224 | @click.option("--fix-format", is_flag=True, help="Fix formatting")
225 | @click.option("--lint", is_flag=True, help="Run linting")
226 | @click.option("--docs", is_flag=True, help="Build documentation")
227 | @click.option("--build-package", is_flag=True, help="Build package")
228 | def precommit(test, check_format, fix_format, lint, docs, build_package):
229 | """Precommit tool for LibSV. If no options are provided, this
230 | tool will run all precommit steps except for --fix-format. If one or more
231 | options are specified then only those precommit steps will be run."""
232 |
233 | # if no flags are provided, then run default configuration
234 | if not any([test, check_format, fix_format, lint, docs, build_package]):
235 | test = True
236 | check_format = True
237 | fix_format = False
238 | lint = True
239 | docs = True
240 | build_package = True
241 |
242 | # Check if in docker container
243 | if not in_docker():
244 | raise OSError(
245 | "Not in a docker container. This script must be run from within a docker"
246 | " container. See README.md for instructions."
247 | )
248 | else:
249 |
250 | # Resolve project root directory before proceeding
251 | if not PROJECT_ROOT.is_dir():
252 | raise FileNotFoundError(
253 | f"Cannot find project root directory: {PROJECT_ROOT}"
254 | )
255 |
256 | if test:
257 | print("\nRunning tests...\n", flush=FLUSH)
258 | run_test()
259 |
260 | if check_format:
261 | print("\nChecking formatting...\n", flush=FLUSH)
262 | run_check_format()
263 |
264 | if fix_format:
265 | print("\nFixing formatting...\n", flush=FLUSH)
266 | run_fix_format()
267 |
268 | if lint:
269 | print("\nLinting...\n", flush=FLUSH)
270 | run_lint()
271 |
272 | if docs:
273 | print("\nBuilding documentation...\n", flush=FLUSH)
274 | run_docs()
275 |
276 | if build_package:
277 | print("\nBuilding package...\n")
278 | run_build_package()
279 |
280 |
281 | if __name__ == "__main__":
282 | precommit()
283 |
--------------------------------------------------------------------------------
/docs/source/contributing.rst:
--------------------------------------------------------------------------------
1 | Contributing
2 | ============
3 |
4 | Contributions are much welcomed and appreciated!
5 |
6 | .. contents::
7 | :depth: 2
8 | :backlinks: none
9 |
10 |
11 | .. _submitfeedback:
12 |
13 | Feature Requests and Feedback
14 | -----------------------------
15 |
16 | We'd like to hear about your propositions and suggestions. Feel free to
17 | `submit them as issues `_ and:
18 |
19 | * Explain in detail how they should work.
20 | * Keep the scope as narrow as possible. This will make it easier to implement.
21 |
22 | Or create/join a discussion in the `LibSV discussion page `_.
23 |
24 |
25 | .. _report_bugs:
26 |
27 | Report Bugs
28 | -----------
29 |
30 | Report bugs for LibSV in the `issue tracker `_.
31 |
32 | If you are reporting a bug, please include:
33 |
34 | * Your operating system name and version.
35 | * Any details about your local setup that might be helpful in troubleshooting,
36 | specifically the Python interpreter version, installed libraries & packages
37 | (i.e. cocotb, verilator, pytest), and whether or not you're using a LibSV Docker
38 | image.
39 | * Detailed steps to reproduce the bug.
40 |
41 | If you can write a demonstration test that currently fails but should pass
42 | (xfail), that is a very useful commit to make as well, even if you cannot
43 | fix the bug itself.
44 |
45 |
46 | .. _setting_up_developers_environment:
47 |
48 | Setting Up the LibSV Developer's Environment
49 | --------------------------------------------
50 |
51 | For anything beyond feature requests, reporting bugs, or very simple fixes, it is *strongly* recommended to setup the LibSV
52 | developer's environment. Especially if you plan to write any SystemVerilog source code, write and/or run Python testbenches,
53 | build the html documentation from source, or modify project workflows/tools/configurations.
54 |
55 | LibSV provides pre-built Docker images, specifically for this purpose, which are available on
56 | `LibSV's Docker Hub page `_. If you don't already have
57 | `Docker `_ installed on your machine, you will need to install it by following the instructions
58 | `here `_.
59 |
60 | Once you have Docker installed you can build and pull the LibSV Docker image by running:
61 |
62 | .. code-block:: bash
63 |
64 | docker build --pull -f Dockerfile.dev \
65 | --build-arg UID=$(id -u) \
66 | --build-arg GID=$(id -g) \
67 | -t libsv .
68 |
69 | By default, this will pull the LibSV Docker image associated with the ``main`` branch, however you can pull a different branch's
70 | LibSV Docker image (or any tagged version) by adding the ``TAG`` build argument:
71 |
72 | .. code-block:: bash
73 |
74 | docker build --pull -f Dockerfile.dev \
75 | --build-arg UID=$(id -u) \
76 | --build-arg GID=$(id -g) \
77 | --build-arg TAG=TAG_NAME \
78 | -t libsv .
79 |
80 | Then, start a new Docker container using the LibSV image and mount the project folder to the container:
81 |
82 | .. code-block:: bash
83 |
84 | docker run --rm -it -v $(pwd):/code libsv
85 |
86 |
87 | .. _fix_bugs:
88 |
89 | Fix Bugs
90 | --------
91 |
92 | Look through the `issue tracker for bugs `_.
93 |
94 | If you're interested in fixing a bug but are unsure about how you can fix it, leave a comment in the specific issue so that
95 | developers can help you find a solution. To indicate that you are going to work on a particular issue, add a comment to that
96 | effect in the issue.
97 |
98 |
99 | .. _implement_features:
100 |
101 | Implement Features
102 | ------------------
103 |
104 | Look through the `issue tracker for enhancements `_.
105 |
106 | If you're interested in implementing a feature but are unsure about how you can do it, leave a comment in the specific issue
107 | so that developers can help you find a solution. To indicate that you are going to work on a particular feature, add a comment
108 | to that effect in the issue.
109 |
110 |
111 | .. _write_documentation:
112 |
113 | Write Documentation
114 | -------------------
115 |
116 | LibSV could always use more documentation. What exactly is needed?
117 |
118 | * Online documentation
119 | * Code comments (including both SystemVerilog source files and python testbenches)
120 |
121 | You can also edit documentation files directly in the GitHub web interface,
122 | without using a local copy. This can be convenient for small fixes.
123 |
124 | .. note::
125 | Build the documentation locally by running the following bash command in a LibSV Docker container
126 | from the top-level project directory:
127 |
128 | .. code-block:: bash
129 |
130 | ./tools/precommit.py --docs
131 |
132 | The built documentation should be available in ``docs/build/html/``.
133 |
134 |
135 | .. _pull_requests:
136 |
137 | Preparing Pull Requests
138 | -----------------------
139 |
140 | Pull requests inform the project's core developers about the
141 | changes you want to review and merge. Pull requests are stored on
142 | `GitHub servers `_.
143 | Once you send a pull request, we can discuss its potential modifications and
144 | even add more commits to it later on. There's an excellent tutorial on how Pull
145 | Requests work in the
146 | `GitHub Help Center `_.
147 |
148 | To prepare a pull request:
149 |
150 | #. Fork the repository.
151 | #. As you make changes and before you commit, run the
152 | `precommit `_ script by
153 | invoking ``./tools/precommit.py`` from the top-level project directory when you're running
154 | in a LibSV Docker container. To see all precommit script options run: ``./tools/precommit.py --help``.
155 | #. Testbenches are run using either ``pytest`` or ``./tools/precommit.py --test``. This will run all
156 | LibSV testbenches. To run only a single testbench, we recommend using ``pytest`` with the ``-k`` flag
157 |
158 | .. code-block:: bash
159 |
160 | pytest -k TESTBENCH_NAME
161 |
162 | #. If you are adding a new SystemVerilog module to the library, you must complete the following checklist:
163 |
164 | * The new SystemVerilog module should be a single ``.sv`` file with a single ``module`` inside that
165 | is added to the right directory within ``libsv/``. File naming convention is all lower-case and
166 | underscores (i.e. ``example_module.sv``).
167 | * The SystemVerilog module should have a corresponding Python testbench that has the same name as
168 | the SystemVerilog source file with a ``test_`` prefix. (i.e. ``test_example_module.sv``). Similarly
169 | to before, this testbench must be added to the right directory within ``tests/``. Take a look
170 | at `existing LibSV testbenches `_ for examples
171 | on how to write a testbench for LibSV.
172 | * Write a testbench that exercises the SystemVerilog module and checks whether the module meets the
173 | functional specifications.
174 | * The SystemVerilog module should have a corresponding ``.rst`` documentation file that has the same
175 | name as the SystemVerilog source file (i.e. ``example_module.rst``). Once again, this documentation
176 | file must be added to the right directory within ``docs/source/``. Take a look at
177 | `existing LibSV docs `_ for examples on
178 | how write documentation for LibSV.
179 |
180 | #. Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section),
181 | please add yourself as a contributor to the ``AUTHORS`` file, in alphabetical order, so we can credit
182 | you for your work!
183 | #. Commit and push once the precommit script passes and you are happy with your changes:
184 |
185 | .. code-block:: bash
186 |
187 | git commit -a -m ""
188 | git push -u
189 |
190 | #. Finally, submit a pull request through GitHub using this data:
191 |
192 | .. code-block:: text
193 |
194 | head repository: YOUR_GITHUB_USERNAME/libsv
195 | compare: your-branch-name
196 |
197 | base repository: bensampson5/libsv
198 | base: main
199 |
200 |
201 | .. _closing_issues:
202 |
203 | Closing Issues
204 | --------------
205 |
206 | When a pull request is submitted to fix an issue, add text like ``Closes #ABC`` to the PR description and/or commits (where ``ABC`` is the
207 | issue number). See the
208 | `GitHub docs `_
209 | for more information.
--------------------------------------------------------------------------------
/tests/coders/test_encoder_8b10b.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.clock import Clock
3 | from cocotb.triggers import FallingEdge
4 | from typing import Tuple
5 | import re
6 | from utils import pytest_cocotb_run_test
7 |
8 |
9 | def test_8b10b_encoder(pytestconfig):
10 | """Pytest fixture for 8B/10B Encoder test"""
11 | pytest_cocotb_run_test(pytestconfig, __name__)
12 |
13 |
14 | def encode_5b6b(i_5b: int, i_disp: int, i_ctrl: bool) -> Tuple[int, int]:
15 | """5b/6b encoder.
16 |
17 | Args:
18 | i_5b (int): input 5b value
19 | i_disp (int): input running disparity (-1 or 1)
20 | i_ctrl (bool): intput control symbol flag
21 |
22 | Returns:
23 | tuple[int, int]: tuple of output 6b value and output running disparity
24 | """
25 |
26 | # lookup table maps EDBCA to iedcba for RD = -1
27 | lut_5b6b_rdm1 = {
28 | # EDCBA: iedcba
29 | 0b00000: 0b111001, # D.00-
30 | 0b00001: 0b101110, # D.01-
31 | 0b00010: 0b101101, # D.02-
32 | 0b00011: 0b100011, # D.03-
33 | 0b00100: 0b101011, # D.04-
34 | 0b00101: 0b100101, # D.05-
35 | 0b00110: 0b100110, # D.06-
36 | 0b00111: 0b000111, # D.07-
37 | 0b01000: 0b100111, # D.08-
38 | 0b01001: 0b101001, # D.09-
39 | 0b01010: 0b101010, # D.10-
40 | 0b01011: 0b001011, # D.11-
41 | 0b01100: 0b101100, # D.12-
42 | 0b01101: 0b001101, # D.13-
43 | 0b01110: 0b001110, # D.14-
44 | 0b01111: 0b111010, # D.15-
45 | 0b10000: 0b110110, # D.16-
46 | 0b10001: 0b110001, # D.17-
47 | 0b10010: 0b110010, # D.18-
48 | 0b10011: 0b010011, # D.19-
49 | 0b10100: 0b110100, # D.20-
50 | 0b10101: 0b010101, # D.21-
51 | 0b10110: 0b010110, # D.22-
52 | 0b10111: 0b010111, # D.23-
53 | 0b11000: 0b110011, # D.24-
54 | 0b11001: 0b011001, # D.25-
55 | 0b11010: 0b011010, # D.26-
56 | 0b11011: 0b011011, # D.27-
57 | 0b11100: 0b011100, # D.28-
58 | 0b11101: 0b011101, # D.29-
59 | 0b11110: 0b011110, # D.30-
60 | 0b11111: 0b110101, # D.31-
61 | }
62 |
63 | o_6b = lut_5b6b_rdm1[i_5b] # lookup 5b/6b RD- value
64 |
65 | # K.28 is unique from the rest of the table
66 | if i_ctrl and i_5b == 28:
67 | if i_disp == -1:
68 | o_6b = 0b111100
69 | else:
70 | o_6b = 0b000011
71 |
72 | elif i_disp == 1: # may have to look up RD+ value
73 |
74 | # check if RD+ value is different than RD- value
75 | if i_5b in [0, 1, 2, 4, 7, 8, 15, 16, 23, 24, 27, 29, 30, 31]:
76 | o_6b = (~o_6b) & 0x3F
77 |
78 | # update running disparity
79 | string_6b = format(o_6b, "06b")
80 | num_ones = string_6b.count("1")
81 | num_zeros = string_6b.count("0")
82 | o_disp = i_disp + num_ones - num_zeros
83 |
84 | # Check for invalid output running disparity
85 | try:
86 | assert o_disp == -1 or o_disp == 1
87 | except AssertionError as e:
88 | i_5b_str = format(i_5b, "#07b")
89 | o_6b_str = format(o_6b, "#08b")
90 | print(
91 | f"Error: Invalid output running disparity value\n"
92 | f" i_5b = {i_5b_str}, i_disp = {i_disp}, i_ctrl = {i_ctrl}\n"
93 | f" o_6b = {o_6b_str}, o_disp = {o_disp}\n"
94 | )
95 | raise e
96 |
97 | return o_6b, o_disp
98 |
99 |
100 | def encode_3b4b(
101 | i_3b: int, i_disp: int, i_ctrl: bool, use_alternate: bool
102 | ) -> Tuple[int, int]:
103 | """3b/4b encoder.
104 |
105 | Args:
106 | i_3b (int): input 3b value
107 | i_disp (int): input running disparity (-1 or 1)
108 | i_ctrl (bool): input control symbol flag
109 | use_alternate (bool): Use alternate flag (for D.x.A7)
110 |
111 | Returns:
112 | tuple[int, int]: tuple of output 4b value and output running disparity
113 | """
114 |
115 | # lookup table maps HGF to jhgf for RD = -1 (data)
116 | lut_3b4b_data_rdm1 = {
117 | # HGF: jhgf
118 | 0b000: 0b1101, # D.x.0-
119 | 0b001: 0b1001, # D.x.1-
120 | 0b010: 0b1010, # D.x.2-
121 | 0b011: 0b0011, # D.x.3-
122 | 0b100: 0b1011, # D.x.4-
123 | 0b101: 0b0101, # D.x.5-
124 | 0b110: 0b0110, # D.x.6-
125 | 0b111: 0b0111, # D.x.P7-
126 | }
127 |
128 | # lookup table maps HGF to jhgf for RD = -1 (control)
129 | lut_3b4b_control_rdm1 = {
130 | # HGF: jhgf
131 | 0b000: 0b1101, # K.x.0-
132 | 0b001: 0b0110, # K.x.1-
133 | 0b010: 0b0101, # K.x.2-
134 | 0b011: 0b0011, # K.x.3-
135 | 0b100: 0b1011, # K.x.4-
136 | 0b101: 0b1010, # K.x.5-
137 | 0b110: 0b1001, # K.x.6-
138 | 0b111: 0b1110, # K.x.7-
139 | }
140 |
141 | # lookup 3b/4b RD- value
142 | o_4b = None
143 | if i_ctrl:
144 | o_4b = lut_3b4b_control_rdm1[i_3b]
145 | else:
146 | if use_alternate and i_3b == 0b111:
147 | o_4b = 0b1110 # RD- value for D.x.A7
148 | else:
149 | o_4b = lut_3b4b_data_rdm1[i_3b]
150 |
151 | # Invert bits to get RD+ value if needed
152 | if i_disp == 1 and (i_ctrl or i_3b in [0, 3, 4, 7]):
153 | o_4b = (~o_4b) & 0xF
154 |
155 | # update running disparity
156 | string_4b = format(o_4b, "04b")
157 | num_ones = string_4b.count("1")
158 | num_zeros = string_4b.count("0")
159 | o_disp = i_disp + num_ones - num_zeros
160 |
161 | # Check for invalid output running disparity
162 | try:
163 | assert o_disp == -1 or o_disp == 1
164 | except AssertionError as e:
165 | i_3b_str = format(i_3b, "#05b")
166 | o_4b_str = format(o_4b, "#06b")
167 | print(
168 | f"Error: Invalid output running disparity value\n"
169 | f" i_3b = {i_3b_str}, i_disp = {i_disp}, i_ctrl = {i_ctrl}\n"
170 | f" use_alternate = {use_alternate}\n"
171 | f" o_4b = {o_4b_str}, o_disp = {o_disp}\n"
172 | )
173 | raise e
174 |
175 | return o_4b, o_disp
176 |
177 |
178 | def encode_8b10b(i_8b: int, i_disp: int, i_ctrl: bool) -> Tuple[int, int, bool]:
179 | """8b/10b encoder.
180 |
181 | Args:
182 | i_8b (int): input 8b value
183 | i_disp (int): input running disparity (-1 or 1)
184 | i_ctrl (bool): input control symbol flag
185 |
186 | Returns:
187 | Tuple[int, int, bool]: Tuple containing output 10b value, output running
188 | disparity, and output code error flag
189 | """
190 |
191 | i_5b = i_8b & 0x1F
192 | i_3b = (i_8b >> 5) & 0x7
193 |
194 | # Step 1: Lookup 5b/6b code
195 | o_6b, o_disp_6b = encode_5b6b(i_5b, i_disp, i_ctrl)
196 |
197 | # Step 2: Select alternate 3b/4b encoding to avoid run of
198 | # five consecutive 0s or 1s when combined with the preceding
199 | # 5b/6b code. D.x.A7 is used only when:
200 | # 1. RD = -1: for x = 17, 18, and 20
201 | # 2. RD = +1: for x = 11, 13, and 14
202 | use_alternate = (o_disp_6b == -1 and i_5b in [17, 18, 20]) or (
203 | o_disp_6b == 1 and i_5b in [11, 13, 14]
204 | )
205 |
206 | # Step 3: Lookup 3b/4b code
207 | o_4b, o_disp_4b = encode_3b4b(i_3b, o_disp_6b, i_ctrl, use_alternate)
208 |
209 | # Combine 6b and 4b codes to a 10b code
210 | o_10b = (o_4b << 6) | o_6b
211 | o_disp = o_disp_4b
212 |
213 | try:
214 | # Check that there isn't a string of 0's or 1's longer than 5
215 | o_10b_s = format(o_10b, "010b")
216 | assert not re.search("0{6,}|1{6,}", o_10b_s)
217 | except AssertionError as e:
218 | i_8b_str = format(i_8b, "#05b")
219 | o_10b_str = format(o_10b, "#012b")
220 | print(
221 | f"Error: 10b value has a string of 0's or 1's longer than 5\n"
222 | f" i_8b = {i_8b_str}, i_rd = {i_disp}, i_ctrl = {i_ctrl}\n"
223 | f" use_alternate = {use_alternate}\n"
224 | f" o_10b = {o_10b_str}, o_rd = {o_disp}\n"
225 | )
226 | raise e
227 |
228 | # Look up illegal codes
229 | o_code_err = False
230 | legal_control_symbols = [
231 | 0b00011100, # K.28.0
232 | 0b00111100, # K.28.1
233 | 0b01011100, # K.28.2
234 | 0b01111100, # K.28.3
235 | 0b10011100, # K.28.4
236 | 0b10111100, # K.28.5
237 | 0b11011100, # K.28.6
238 | 0b11111100, # K.28.7
239 | 0b11110111, # K.23.7
240 | 0b11111011, # K.27.7
241 | 0b11111101, # K.29.7
242 | 0b11111110, # K.30.7
243 | ]
244 | if i_ctrl and i_8b not in legal_control_symbols:
245 | o_code_err = True
246 |
247 | return o_10b, o_disp, o_code_err
248 |
249 |
250 | @cocotb.test()
251 | async def cocotb_test_encoder_8b10b(dut):
252 | """8B/10B Encoder test"""
253 |
254 | # Create clock
255 | cocotb.start_soon(Clock(dut.i_clk, 2).start())
256 |
257 | # Assert reset and check output and
258 | # internal running disparity
259 | dut.i_reset_n.value = 0
260 | await FallingEdge(dut.i_clk)
261 | assert int(dut.o_10b) == 0
262 | assert int(dut.o_code_err) == 0
263 | assert int(dut.rd) == 0
264 |
265 | # Check that enable doesn't change outputs when disabled
266 | dut.i_reset_n.value = 1 # de-assert reset
267 | dut.i_en.value = 1 # enable 8b10b encoder
268 | dut.i_8b.value = 0 # D.00.0
269 | dut.i_ctrl.value = 0 # data symbol
270 | await FallingEdge(dut.i_clk) # 1 clock tick
271 | prev_o_10b = int(dut.o_10b) # capture 10b output
272 | prev_o_code_err = int(dut.o_code_err) # capture code error
273 | prev_rd = int(dut.rd) # capture running disparity
274 | dut.i_en.value = 0 # disable 8b10b encoder
275 |
276 | # Pick a value that after the first 8b value would change both
277 | # the output 10b value, internal running disparity, and generate
278 | # a code error
279 | dut.i_ctrl.value = 1 # control symbol
280 | dut.i_8b.value = 0b00000011 # K.03.0
281 |
282 | await FallingEdge(dut.i_clk) # 1 clock tick
283 | assert prev_o_10b == int(dut.o_10b) # output should not have changed
284 | assert prev_o_code_err == int(dut.o_code_err) # code error should not have changed
285 | assert prev_rd == int(dut.rd) # running disparity should not have changed
286 |
287 | # Test 8b/10b encoding look-up table
288 | dut.i_reset_n.value = 1
289 | dut.i_en.value = 1
290 | for i in range(2**10):
291 |
292 | # Parse out input values
293 | i_8b = i & 0xFF
294 | i_disp = (i >> 8) & 1
295 | i_ctrl = (i >> 9) & 1
296 |
297 | # Run the software 8b10b encoder
298 | py_i_8b = i_8b
299 | py_i_disp = (i_disp * 2) - 1
300 | py_i_ctrl = bool(i_ctrl)
301 | py_o_10b, py_o_disp, py_o_code_err = encode_8b10b(py_i_8b, py_i_disp, py_i_ctrl)
302 |
303 | # Calculate expected dut outputs using software
304 | # 8b/10b encoder results
305 | o_10b = py_o_10b
306 | o_disp = (py_o_disp + 1) // 2
307 | o_code_err = int(py_o_code_err)
308 |
309 | # Push inputs to dut
310 | dut.i_8b.value = i_8b
311 | dut.i_ctrl.value = i_ctrl
312 |
313 | # Force internal running disparity to specific value
314 | dut.rd.value = i_disp
315 |
316 | d_prev_rd = int(dut.rd) # save previous RD value
317 |
318 | # Step 1 clock tick
319 | await FallingEdge(dut.i_clk) # 1 clock tick
320 |
321 | # Check actual outputs vs expected outputs
322 | try:
323 | assert int(dut.o_10b) == o_10b
324 | assert int(dut.rd) == o_disp
325 | assert int(dut.o_code_err) == o_code_err
326 | except AssertionError as e:
327 | i_8b_s = format(i_8b, "#010b")
328 | i_ctrl_s = format(i_ctrl, "#03b")
329 | d_i_8b_s = "0b" + str(dut.i_8b.value)
330 | d_i_ctrl_s = "0b" + str(dut.i_ctrl.value)
331 | d_prev_rd_s = format(d_prev_rd, "#03b")
332 | d_rd_s = "0b" + str(dut.rd.value)
333 | d_o_10b_s = "0b" + str(dut.o_10b.value)
334 | d_o_code_err_s = "0b" + str(dut.o_code_err.value)
335 | o_10b_s = format(o_10b, "#012b")
336 | o_code_err_s = format(o_code_err, "#03b")
337 | print(
338 | f"Error: Actual outputs do not match expected outputs\n"
339 | f" i_8b = {i_8b_s}, i_ctrl = {i_ctrl_s}\n"
340 | f" dut.i_8b = {d_i_8b_s}, dut.i_ctrl = {d_i_ctrl_s}\n"
341 | f" previous dut.rd = {d_prev_rd_s}, current dut.rd = {d_rd_s}\n"
342 | f" dut.o_10b = {d_o_10b_s}, dut.o_code_err = {d_o_code_err_s}\n"
343 | f" o_10b = {o_10b_s}, o_code_err = {o_code_err_s}\n"
344 | )
345 | raise e
346 |
--------------------------------------------------------------------------------
/docs/source/encoder_8b10b.rst:
--------------------------------------------------------------------------------
1 | ==============
2 | 8B/10B Encoder
3 | ==============
4 |
5 | 8B/10B is a line code that maps 8-bit words to 10-bit symbols to achieve DC-balance and bounded
6 | disparity, while at the same time provide enough state changes to allow reasonable clock recovery. This
7 | 8B/10B encoder design implements
8 | `IBM's 8B/10B coding scheme `_.
9 |
10 | DC-free 8b/10b coding requires that the long-term ratio of ones and zeros transmitted is exactly 50%. To
11 | achieve this, the difference between the number of 1s and 0s transmitted is always limited to ±2, so
12 | the difference at the end of each symbol will always be either +1 or -1. This difference is known
13 | as the *running disparity* (RD). The IBM implementation needs only two states for the running disparity,
14 | -1 and +1, where the RD always starts at -1.
15 |
16 | To simplify the coding algorithm, the IBM 8b/10b coding implementation breaks down the 8b/10b coding
17 | into 5b/6b and 3b/4b subcodings. Then, the running disparity is evaluated over each 6b or 4b code as
18 | it is transmitted or received. The rules for calculating running disparity are:
19 |
20 | 1. If the disparity of the 6b or 4b codeword is 0 (equal number of 1s and 0s) then the output running
21 | disparity is equal to the input running disparity (i.e. -1 -> -1, +1 -> +1).
22 | 2. If the disparity of the 6b or 4b codeword is not 0 (i.e. ±2, ±4, ±6) then the output running disparity
23 | is equal to the complement of the input running disparity (i.e. -1 -> +1, +1 -> -1)
24 |
25 | This core keeps track of the running disparity internally so the user does not need to implement any
26 | additional logic to determine it. The user can, however, control the running disparity by asserting
27 | the active-low reset signal ``i_reset_n`` to reset the running disparity to -1.
28 |
29 | Additional control and error status features are provided with this design and are described below:
30 |
31 | * ``i_en`` is an input enable signal that controls whether or not to perform 8b/10b
32 | encoding. While deasserted, the core will ignore any further input, maintain current outputs,
33 | and maintain the current running disparity.
34 | * ``i_ctrl`` is an input control symbol select signal that will have the encoder output a
35 | control symbol (K.x.y) if high, otherwise it will output a data symbol (D.x.y).
36 | * ``o_code_err`` is an error status signal that indicates when the user encoded an illegal 8b/10b
37 | code. As all data symbols are valid codes, this signal will only be asserted if an illegal
38 | control symbol is encoded because only 12 control symbols are allowed to be sent. For more
39 | information on the allowed control symbols see the :ref:`Control Symbols table `.
40 |
41 | IBM's 8B/10B implementation is defined by partitioning the coder into 5B/6B and 3B/4B subordinate coders
42 | as described in the tables below. Using these tables, a given input 8-bit value, ``HGFEDCBA``, along
43 | with the current running disparity and a control symbol select signal, can be encoded to its corresponding
44 | 10-bit value, ``jhgfiedcba``.
45 |
46 |
47 | .. table:: 5B/6B Coding Table
48 |
49 | +--------------+---------+----------+----------------+---------+----------+
50 | | Input | RD = −1 | RD = +1 | Input | RD = −1 | RD = +1 |
51 | +======+=======+=========+==========+========+=======+=========+==========+
52 | | Code | EDCBA | abcdei | Code | EDCBA | abcdei |
53 | +------+-------+---------+----------+--------+-------+---------+----------+
54 | | D.00 | 00000 | 100111 | 011000 | D.16 | 10000 | 011011 | 100100 |
55 | +------+-------+---------+----------+--------+-------+---------+----------+
56 | | D.01 | 00001 | 011101 | 100010 | D.17 | 10001 | 100011 |
57 | +------+-------+---------+----------+--------+-------+--------------------+
58 | | D.02 | 00010 | 101101 | 010010 | D.18 | 10010 | 010011 |
59 | +------+-------+---------+----------+--------+-------+--------------------+
60 | | D.03 | 00011 | 110001 | D.19 | 10011 | 110010 |
61 | +------+-------+---------+----------+--------+-------+--------------------+
62 | | D.04 | 00100 | 110101 | 001010 | D.20 | 10100 | 001011 |
63 | +------+-------+---------+----------+--------+-------+--------------------+
64 | | D.05 | 00101 | 101001 | D.21 | 10101 | 101010 |
65 | +------+-------+--------------------+--------+-------+--------------------+
66 | | D.06 | 00110 | 011001 | D.22 | 10110 | 011010 |
67 | +------+-------+---------+----------+--------+-------+---------+----------+
68 | | D.07 | 00111 | 111000 | 000111 | D.23 † | 10111 | 111010 | 000101 |
69 | +------+-------+---------+----------+--------+-------+---------+----------+
70 | | D.08 | 01000 | 111001 | 000110 | D.24 | 11000 | 110011 | 001100 |
71 | +------+-------+---------+----------+--------+-------+---------+----------+
72 | | D.09 | 01001 | 100101 | D.25 | 11001 | 100110 |
73 | +------+-------+--------------------+--------+-------+--------------------+
74 | | D.10 | 01010 | 010101 | D.26 | 11010 | 010110 |
75 | +------+-------+--------------------+--------+-------+---------+----------+
76 | | D.11 | 01011 | 110100 | D.27 † | 11011 | 110110 | 001001 |
77 | +------+-------+--------------------+--------+-------+---------+----------+
78 | | D.12 | 01100 | 001101 | D.28 | 11100 | 001110 |
79 | +------+-------+--------------------+--------+-------+---------+----------+
80 | | D.13 | 01101 | 101100 | D.29 † | 11101 | 101110 | 010001 |
81 | +------+-------+--------------------+--------+-------+---------+----------+
82 | | D.14 | 01110 | 011100 | D.30 † | 11110 | 011110 | 100001 |
83 | +------+-------+---------+----------+--------+-------+---------+----------+
84 | | D.15 | 01111 | 010111 | 101000 | D.31 | 11111 | 101011 | 010100 |
85 | +------+-------+---------+----------+--------+-------+---------+----------+
86 | | | K.28 ‡ | 11100 | 001111 | 110000 |
87 | +-----------------------------------+--------+-------+---------+----------+
88 |
89 | † *also used for the 5b/6b code of K.x.7*
90 |
91 | ‡ *exclusively used for the 5b/6b code of K.28.y*
92 |
93 |
94 | .. table:: 3B/4B Coding Table
95 |
96 | +----------------+---------+----------+---------------+---------+----------+
97 | | Input | RD = −1 | RD = +1 | Input | RD = −1 | RD = +1 |
98 | +----------+-----+---------+----------+---------+-----+---------+----------+
99 | | Code | HGF | fghj | Code | HGF | fghj |
100 | +==========+=====+=========+==========+=========+=====+=========+==========+
101 | | D.x.0 | 000 | 1011 | 0100 | K.x.0 | 000 | 1011 | 0100 |
102 | +----------+-----+---------+----------+---------+-----+---------+----------+
103 | | D.x.1 | 001 | 1001 | K.x.1 ‡ | 001 | 0110 | 1001 |
104 | +----------+-----+--------------------+---------+-----+---------+----------+
105 | | D.x.2 | 010 | 0101 | K.x.2 | 010 | 1010 | 0101 |
106 | +----------+-----+---------+----------+---------+-----+---------+----------+
107 | | D.x.3 | 011 | 1100 | 0011 | K.x.3 | 011 | 1100 | 0011 |
108 | +----------+-----+---------+----------+---------+-----+---------+----------+
109 | | D.x.4 | 100 | 1101 | 0010 | K.x.4 | 100 | 1101 | 0010 |
110 | +----------+-----+---------+----------+---------+-----+---------+----------+
111 | | D.x.5 | 101 | 1010 | K.x.5 ‡ | 101 | 0101 | 1010 |
112 | +----------+-----+--------------------+---------+-----+---------+----------+
113 | | D.x.6 | 110 | 0110 | K.x.6 | 110 | 1001 | 0110 |
114 | +----------+-----+---------+----------+---------+-----+---------+----------+
115 | | D.x.P7 † | | 1110 | 0001 | K.x.7 ‡ | 111 | 0111 | 1000 |
116 | +----------+ 111 +---------+----------+---------+-----+---------+----------+
117 | | D.x.A7 † | | 0111 | 1000 | |
118 | +----------+-----+---------+----------+------------------------------------+
119 |
120 | † *For D.x.7, either the Primary (D.x.P7), or the Alternate (D.x.A7) encoding must be selected*
121 | *in order to avoid a run of five consecutive 0s or 1s when combined with the preceding 5b/6b code.*
122 | *Sequences of exactly five identical bits are used in comma symbols for synchronization issues.*
123 | *D.x.A7 is used only when:*
124 |
125 | * *RD = −1: for x = 17, 18 and 20*
126 | * *RD = +1: for x = 11, 13 and 14*
127 |
128 | *With x = 23, x = 27, x = 29, and x = 30, the 3b/4b code portion used for control symbols K.x.7 is*
129 | *the same as that for D.x.A7. Any other D.x.A7 code can't be used as it would result in chances for*
130 | *misaligned comma sequences.*
131 |
132 | ‡ *Only K.28.1, K.28.5, and K.28.7 generate comma symbols, that contain a bit sequence of five 0s or*
133 | *1s. The symbol has the format 110000 01xx or 001111 10xx.*
134 |
135 | .. _control_symbols:
136 |
137 | Control symbols are used in 8b/10b coding for low-level control functions such as comma symbols for
138 | synchronization, loop arbitration, fill words, link resets, etc. The way in which the control
139 | symbols are used is defined by the protocol standard (i.e. Ethernet, Fibre Channel, PCIe) but
140 | 8b/10b coding only allows 12 control symbols to be sent.
141 |
142 |
143 | .. table:: Control Symbols
144 |
145 | +----------------------------------+-------------+-------------+
146 | | Input | RD = −1 | RD = +1 |
147 | +----------+-----+-----+-----------+-------------+-------------+
148 | | Symbol | DEC | HEX | HGF EDCBA | abcdei fghj | abcdei fghj |
149 | +==========+=====+=====+===========+=============+=============+
150 | | K.28.0 | 28 | 1C | 000 11100 | 001111 0100 | 110000 1011 |
151 | +----------+-----+-----+-----------+-------------+-------------+
152 | | K.28.1 † | 60 | 3C | 001 11100 | 001111 1001 | 110000 0110 |
153 | +----------+-----+-----+-----------+-------------+-------------+
154 | | K.28.2 | 92 | 5C | 010 11100 | 001111 0101 | 110000 1010 |
155 | +----------+-----+-----+-----------+-------------+-------------+
156 | | K.28.3 | 124 | 7C | 011 11100 | 001111 0011 | 110000 1100 |
157 | +----------+-----+-----+-----------+-------------+-------------+
158 | | K.28.4 | 156 | 9C | 100 11100 | 001111 0010 | 110000 1101 |
159 | +----------+-----+-----+-----------+-------------+-------------+
160 | | K.28.5 † | 188 | BC | 101 11100 | 001111 1010 | 110000 0101 |
161 | +----------+-----+-----+-----------+-------------+-------------+
162 | | K.28.6 | 220 | DC | 110 11100 | 001111 0110 | 110000 1001 |
163 | +----------+-----+-----+-----------+-------------+-------------+
164 | | K.28.7 ‡ | 252 | FC | 111 11100 | 001111 1000 | 110000 0111 |
165 | +----------+-----+-----+-----------+-------------+-------------+
166 | | K.23.7 | 247 | F7 | 111 10111 | 111010 1000 | 000101 0111 |
167 | +----------+-----+-----+-----------+-------------+-------------+
168 | | K.27.7 | 251 | FB | 111 11011 | 110110 1000 | 001001 0111 |
169 | +----------+-----+-----+-----------+-------------+-------------+
170 | | K.29.7 | 253 | FD | 111 11101 | 101110 1000 | 010001 0111 |
171 | +----------+-----+-----+-----------+-------------+-------------+
172 | | K.30.7 | 254 | FE | 111 11110 | 011110 1000 | 100001 0111 |
173 | +----------+-----+-----+-----------+-------------+-------------+
174 |
175 | † *Within the control symbols, K.28.1, K.28.5, and K.28.7 are "comma symbols". Comma symbols*
176 | *are used for synchronization (finding the alignment of the 8b/10b codes within a bit-stream).*
177 | *If K.28.7 is not used, the unique comma sequences 00111110 or 11000001 cannot be found at any*
178 | *bit position within any combination of normal codes.*
179 |
180 | ‡ *If K.28.7 is allowed in the actual coding, a more complex definition of the synchronization*
181 | *pattern than suggested by † needs to be used, as a combination of K.28.7 with several other*
182 | *codes forms a false misaligned comma symbol overlapping the two codes. A sequence of multiple*
183 | *K.28.7 codes is not allowable in any case, as this would result in undetectable misaligned*
184 | *comma symbols. K.28.7 is the only comma symbol that cannot be the result of a single bit error*
185 | *in the data stream.*
186 |
187 |
188 | Parameters
189 | ----------
190 | - None
191 |
192 | Ports
193 | -----
194 | - ``i_clk`` : input clock
195 | - ``i_reset_n`` : input asynchronous active-low reset
196 | - ``i_en`` : input active-high enable
197 | - ``i_8b`` : input 8-bit value (bit-order is ``HGFEDCBA`` where ``A`` is the lsb)
198 | - ``i_ctrl`` : input control symbol select (``0`` = data symbol, ``1`` = control symbol)
199 | - ``o_10b`` : output 10-bit value (bit-order is ``jhgfiedcba`` where ``a`` is the lsb)
200 | - ``o_code_err`` : output code error
201 |
202 | Source Code
203 | -----------
204 | .. literalinclude:: ../../libsv/coders/encoder_8b10b.sv
205 | :language: systemverilog
206 | :linenos:
207 | :caption: encoder_8b10b.sv
208 |
--------------------------------------------------------------------------------
/docs/source/decoder_8b10b.rst:
--------------------------------------------------------------------------------
1 | ==============
2 | 8B/10B Decoder
3 | ==============
4 |
5 | 8B/10B is a line code that maps 8-bit words to 10-bit symbols to achieve DC-balance and bounded
6 | disparity, while at the same time provide enough state changes to allow reasonable clock recovery. This
7 | 8B/10B decoder design implements
8 | `IBM's 8B/10B coding scheme `_.
9 |
10 | DC-free 8b/10b coding requires that the long-term ratio of ones and zeros transmitted is exactly 50%. To
11 | achieve this, the difference between the number of 1s and 0s transmitted is always limited to ±2, so
12 | the difference at the end of each symbol will always be either +1 or -1. This difference is known
13 | as the *running disparity* (RD). The IBM implementation needs only two states for the running disparity,
14 | -1 and +1, where the RD always starts at -1.
15 |
16 | To simplify the coding algorithm, the IBM 8b/10b coding implementation breaks down the 8b/10b coding
17 | into 5b/6b and 3b/4b subcodings. Then, the running disparity is evaluated over each 6b or 4b code as
18 | it is transmitted or received. The rules for calculating running disparity are:
19 |
20 | 1. If the disparity of the 6b or 4b codeword is 0 (equal number of 1s and 0s) then the output running
21 | disparity is equal to the input running disparity (i.e. -1 -> -1, +1 -> +1).
22 | 2. If the disparity of the 6b or 4b codeword is not 0 (i.e. ±2, ±4, ±6) then the output running disparity
23 | is equal to the complement of the input running disparity (i.e. -1 -> +1, +1 -> -1)
24 |
25 | This core keeps track of the running disparity internally so the user does not need to implement any
26 | additional logic to determine it. The user can, however, control the running disparity by asserting
27 | the active-low reset signal ``i_reset_n`` to reset the running disparity to -1.
28 |
29 | Additional control and error status features are provided with this design and are described below:
30 |
31 | * ``i_en`` is an input enable signal that controls whether or not to perform 8b/10b
32 | decoding. While deasserted, the core will ignore any further input, maintain current outputs,
33 | and maintain the current running disparity.
34 | * ``o_ctrl`` is an output control symbol flag which the decoder uses to indicate whether a received
35 | 10b value is a control symbol (K.x.y, ``o_ctrl`` = 1) or a data symbol (D.x.y, ``o_ctrl`` = 0).
36 | * ``o_code_err`` is an error status signal that indicates when an illegal 8b/10b code is received by
37 | the decoder.
38 | * ``o_disp_err`` is an error status signal that indicates when a disparity error is detected in the
39 | 10b value received by the decoder.
40 |
41 | Two error output signals, ``o_code_err`` and ``o_disp_err``, are provided for the decoder as there are
42 | certain input combinations that may only generate one of the two error types in decoding 10b values -
43 | so both are provided to the user to allow them to handle the two error types separately should they want
44 | to.
45 |
46 | IBM's 8B/10B implementation is defined by partitioning the coder into 5B/6B and 3B/4B subordinate coders
47 | as described in the tables below. Using these tables, a given input 10-bit value, ``jhgfiedcba``, along
48 | with the current running disparity, can be decoded to its corresponding 8-bit value, ``HGFEDCBA``, along
49 | with an indication if the received symbol is a control or data symbol.
50 |
51 |
52 | .. table:: 5B/6B Coding Table
53 |
54 | +--------------+---------+----------+----------------+---------+----------+
55 | | Input | RD = −1 | RD = +1 | Input | RD = −1 | RD = +1 |
56 | +======+=======+=========+==========+========+=======+=========+==========+
57 | | Code | EDCBA | abcdei | Code | EDCBA | abcdei |
58 | +------+-------+---------+----------+--------+-------+---------+----------+
59 | | D.00 | 00000 | 100111 | 011000 | D.16 | 10000 | 011011 | 100100 |
60 | +------+-------+---------+----------+--------+-------+---------+----------+
61 | | D.01 | 00001 | 011101 | 100010 | D.17 | 10001 | 100011 |
62 | +------+-------+---------+----------+--------+-------+--------------------+
63 | | D.02 | 00010 | 101101 | 010010 | D.18 | 10010 | 010011 |
64 | +------+-------+---------+----------+--------+-------+--------------------+
65 | | D.03 | 00011 | 110001 | D.19 | 10011 | 110010 |
66 | +------+-------+---------+----------+--------+-------+--------------------+
67 | | D.04 | 00100 | 110101 | 001010 | D.20 | 10100 | 001011 |
68 | +------+-------+---------+----------+--------+-------+--------------------+
69 | | D.05 | 00101 | 101001 | D.21 | 10101 | 101010 |
70 | +------+-------+--------------------+--------+-------+--------------------+
71 | | D.06 | 00110 | 011001 | D.22 | 10110 | 011010 |
72 | +------+-------+---------+----------+--------+-------+---------+----------+
73 | | D.07 | 00111 | 111000 | 000111 | D.23 † | 10111 | 111010 | 000101 |
74 | +------+-------+---------+----------+--------+-------+---------+----------+
75 | | D.08 | 01000 | 111001 | 000110 | D.24 | 11000 | 110011 | 001100 |
76 | +------+-------+---------+----------+--------+-------+---------+----------+
77 | | D.09 | 01001 | 100101 | D.25 | 11001 | 100110 |
78 | +------+-------+--------------------+--------+-------+--------------------+
79 | | D.10 | 01010 | 010101 | D.26 | 11010 | 010110 |
80 | +------+-------+--------------------+--------+-------+---------+----------+
81 | | D.11 | 01011 | 110100 | D.27 † | 11011 | 110110 | 001001 |
82 | +------+-------+--------------------+--------+-------+---------+----------+
83 | | D.12 | 01100 | 001101 | D.28 | 11100 | 001110 |
84 | +------+-------+--------------------+--------+-------+---------+----------+
85 | | D.13 | 01101 | 101100 | D.29 † | 11101 | 101110 | 010001 |
86 | +------+-------+--------------------+--------+-------+---------+----------+
87 | | D.14 | 01110 | 011100 | D.30 † | 11110 | 011110 | 100001 |
88 | +------+-------+---------+----------+--------+-------+---------+----------+
89 | | D.15 | 01111 | 010111 | 101000 | D.31 | 11111 | 101011 | 010100 |
90 | +------+-------+---------+----------+--------+-------+---------+----------+
91 | | | K.28 ‡ | 11100 | 001111 | 110000 |
92 | +-----------------------------------+--------+-------+---------+----------+
93 |
94 | † *also used for the 5b/6b code of K.x.7*
95 |
96 | ‡ *exclusively used for the 5b/6b code of K.28.y*
97 |
98 |
99 | .. table:: 3B/4B Coding Table
100 |
101 | +----------------+---------+----------+---------------+---------+----------+
102 | | Input | RD = −1 | RD = +1 | Input | RD = −1 | RD = +1 |
103 | +----------+-----+---------+----------+---------+-----+---------+----------+
104 | | Code | HGF | fghj | Code | HGF | fghj |
105 | +==========+=====+=========+==========+=========+=====+=========+==========+
106 | | D.x.0 | 000 | 1011 | 0100 | K.x.0 | 000 | 1011 | 0100 |
107 | +----------+-----+---------+----------+---------+-----+---------+----------+
108 | | D.x.1 | 001 | 1001 | K.x.1 ‡ | 001 | 0110 | 1001 |
109 | +----------+-----+--------------------+---------+-----+---------+----------+
110 | | D.x.2 | 010 | 0101 | K.x.2 | 010 | 1010 | 0101 |
111 | +----------+-----+---------+----------+---------+-----+---------+----------+
112 | | D.x.3 | 011 | 1100 | 0011 | K.x.3 | 011 | 1100 | 0011 |
113 | +----------+-----+---------+----------+---------+-----+---------+----------+
114 | | D.x.4 | 100 | 1101 | 0010 | K.x.4 | 100 | 1101 | 0010 |
115 | +----------+-----+---------+----------+---------+-----+---------+----------+
116 | | D.x.5 | 101 | 1010 | K.x.5 ‡ | 101 | 0101 | 1010 |
117 | +----------+-----+--------------------+---------+-----+---------+----------+
118 | | D.x.6 | 110 | 0110 | K.x.6 | 110 | 1001 | 0110 |
119 | +----------+-----+---------+----------+---------+-----+---------+----------+
120 | | D.x.P7 † | | 1110 | 0001 | K.x.7 ‡ | 111 | 0111 | 1000 |
121 | +----------+ 111 +---------+----------+---------+-----+---------+----------+
122 | | D.x.A7 † | | 0111 | 1000 | |
123 | +----------+-----+---------+----------+------------------------------------+
124 |
125 | † *For D.x.7, either the Primary (D.x.P7), or the Alternate (D.x.A7) encoding must be selected*
126 | *in order to avoid a run of five consecutive 0s or 1s when combined with the preceding 5b/6b code.*
127 | *Sequences of exactly five identical bits are used in comma symbols for synchronization issues.*
128 | *D.x.A7 is used only when:*
129 |
130 | * *RD = −1: for x = 17, 18 and 20*
131 | * *RD = +1: for x = 11, 13 and 14*
132 |
133 | *With x = 23, x = 27, x = 29, and x = 30, the 3b/4b code portion used for control symbols K.x.7 is*
134 | *the same as that for D.x.A7. Any other D.x.A7 code can't be used as it would result in chances for*
135 | *misaligned comma sequences.*
136 |
137 | ‡ *Only K.28.1, K.28.5, and K.28.7 generate comma symbols, that contain a bit sequence of five 0s or*
138 | *1s. The symbol has the format 110000 01xx or 001111 10xx.*
139 |
140 |
141 | Control symbols are used in 8b/10b coding for low-level control functions such as comma symbols for
142 | synchronization, loop arbitration, fill words, link resets, etc. The way in which the control
143 | symbols are used is defined by the protocol standard (i.e. Ethernet, Fibre Channel, PCIe) but
144 | 8b/10b coding only allows 12 control symbols to be sent.
145 |
146 |
147 | .. table:: Control Symbols
148 |
149 | +----------------------------------+-------------+-------------+
150 | | Input | RD = −1 | RD = +1 |
151 | +----------+-----+-----+-----------+-------------+-------------+
152 | | Symbol | DEC | HEX | HGF EDCBA | abcdei fghj | abcdei fghj |
153 | +==========+=====+=====+===========+=============+=============+
154 | | K.28.0 | 28 | 1C | 000 11100 | 001111 0100 | 110000 1011 |
155 | +----------+-----+-----+-----------+-------------+-------------+
156 | | K.28.1 † | 60 | 3C | 001 11100 | 001111 1001 | 110000 0110 |
157 | +----------+-----+-----+-----------+-------------+-------------+
158 | | K.28.2 | 92 | 5C | 010 11100 | 001111 0101 | 110000 1010 |
159 | +----------+-----+-----+-----------+-------------+-------------+
160 | | K.28.3 | 124 | 7C | 011 11100 | 001111 0011 | 110000 1100 |
161 | +----------+-----+-----+-----------+-------------+-------------+
162 | | K.28.4 | 156 | 9C | 100 11100 | 001111 0010 | 110000 1101 |
163 | +----------+-----+-----+-----------+-------------+-------------+
164 | | K.28.5 † | 188 | BC | 101 11100 | 001111 1010 | 110000 0101 |
165 | +----------+-----+-----+-----------+-------------+-------------+
166 | | K.28.6 | 220 | DC | 110 11100 | 001111 0110 | 110000 1001 |
167 | +----------+-----+-----+-----------+-------------+-------------+
168 | | K.28.7 ‡ | 252 | FC | 111 11100 | 001111 1000 | 110000 0111 |
169 | +----------+-----+-----+-----------+-------------+-------------+
170 | | K.23.7 | 247 | F7 | 111 10111 | 111010 1000 | 000101 0111 |
171 | +----------+-----+-----+-----------+-------------+-------------+
172 | | K.27.7 | 251 | FB | 111 11011 | 110110 1000 | 001001 0111 |
173 | +----------+-----+-----+-----------+-------------+-------------+
174 | | K.29.7 | 253 | FD | 111 11101 | 101110 1000 | 010001 0111 |
175 | +----------+-----+-----+-----------+-------------+-------------+
176 | | K.30.7 | 254 | FE | 111 11110 | 011110 1000 | 100001 0111 |
177 | +----------+-----+-----+-----------+-------------+-------------+
178 |
179 | † *Within the control symbols, K.28.1, K.28.5, and K.28.7 are "comma symbols". Comma symbols*
180 | *are used for synchronization (finding the alignment of the 8b/10b codes within a bit-stream).*
181 | *If K.28.7 is not used, the unique comma sequences 00111110 or 11000001 cannot be found at any*
182 | *bit position within any combination of normal codes.*
183 |
184 | ‡ *If K.28.7 is allowed in the actual coding, a more complex definition of the synchronization*
185 | *pattern than suggested by † needs to be used, as a combination of K.28.7 with several other*
186 | *codes forms a false misaligned comma symbol overlapping the two codes. A sequence of multiple*
187 | *K.28.7 codes is not allowable in any case, as this would result in undetectable misaligned*
188 | *comma symbols. K.28.7 is the only comma symbol that cannot be the result of a single bit error*
189 | *in the data stream.*
190 |
191 |
192 | Parameters
193 | ----------
194 | - None
195 |
196 | Ports
197 | -----
198 | - ``i_clk`` : input clock
199 | - ``i_reset_n`` : input asynchronous active-low reset
200 | - ``i_en`` : input active-high enable
201 | - ``i_10b`` : input 10-bit binary value (bit-order is ``jhgfiedcba`` where ``a`` is the lsb)
202 | - ``o_8b`` : output 8-bit binary value (bit-order is ``HGFEDCBA`` where ``A`` is the lsb)
203 | - ``o_ctrl`` : output control symbol indicator flag (``0`` = data symbol, ``1`` = control symbol)
204 | - ``o_code_err`` : output code error bit (``0`` = no code error, ``1`` = code error)
205 | - ``o_disp_err`` : output disparity error bit (``0`` = no disparity error, ``1`` = disparity error)
206 |
207 | Source Code
208 | -----------
209 | .. literalinclude:: ../../libsv/coders/decoder_8b10b.sv
210 | :language: systemverilog
211 | :linenos:
212 | :caption: decoder_8b10b.sv
213 |
--------------------------------------------------------------------------------
/tests/coders/test_decoder_8b10b.py:
--------------------------------------------------------------------------------
1 | import cocotb
2 | from cocotb.clock import Clock
3 | from cocotb.triggers import FallingEdge
4 | from typing import Tuple
5 | from utils import pytest_cocotb_run_test
6 |
7 |
8 | def test_8b10b_decoder(pytestconfig):
9 | """Pytest fixture for 8B/10B Decoder test"""
10 | pytest_cocotb_run_test(pytestconfig, __name__)
11 |
12 |
13 | def decode_5b6b(i_6b: int, i_disp: int) -> Tuple[int, int, bool, bool]:
14 | """5b/6b decoder.
15 |
16 | Args:
17 | i_6b (int): input 6b value
18 | i_disp (int): input disparity (-1 or 1)
19 |
20 | Returns:
21 | tuple[int, int, bool, bool]: tuple of output 5b value, output disparity,
22 | control symbol flag, code error flag, and disparity error flag
23 | """
24 |
25 | # lookup table maps iedcba to EDBCA
26 | lut_6b5b = {
27 | # iebcba: EDCBA
28 | 0b000000: None, # Illegal code
29 | 0b000001: None, # Illegal code
30 | 0b000010: None, # Illegal code
31 | 0b000011: 0b11100, # K.28+
32 | 0b000100: None, # Illegal code
33 | 0b000101: 0b01111, # D.15+
34 | 0b000110: 0b00000, # D.0+
35 | 0b000111: 0b00111, # D.7-
36 | 0b001000: None, # Illegal code
37 | 0b001001: 0b10000, # D.16+
38 | 0b001010: 0b11111, # D.31+
39 | 0b001011: 0b01011, # D.11
40 | 0b001100: 0b11000, # D.24+
41 | 0b001101: 0b01101, # D.13
42 | 0b001110: 0b01110, # D.14
43 | 0b001111: None, # Illegal code (not used)
44 | 0b010000: None, # Illegal code
45 | 0b010001: 0b00001, # D.01+
46 | 0b010010: 0b00010, # D.02+
47 | 0b010011: 0b10011, # D.19
48 | 0b010100: 0b00100, # D.04+
49 | 0b010101: 0b10101, # D.21
50 | 0b010110: 0b10110, # D.22
51 | 0b010111: 0b10111, # D.23-
52 | 0b011000: 0b01000, # D.08+
53 | 0b011001: 0b11001, # D.25
54 | 0b011010: 0b11010, # D.26
55 | 0b011011: 0b11011, # D.27-
56 | 0b011100: 0b11100, # D.28
57 | 0b011101: 0b11101, # D.29-
58 | 0b011110: 0b11110, # D.30-
59 | 0b011111: None, # Illegal code
60 | 0b100000: None, # Illegal code
61 | 0b100001: 0b11110, # D.30+
62 | 0b100010: 0b11101, # D.29+
63 | 0b100011: 0b00011, # D.03
64 | 0b100100: 0b11011, # D.27+
65 | 0b100101: 0b00101, # D.05
66 | 0b100110: 0b00110, # D.06
67 | 0b100111: 0b01000, # D.08-
68 | 0b101000: 0b10111, # D.23+
69 | 0b101001: 0b01001, # D.09
70 | 0b101010: 0b01010, # D.10
71 | 0b101011: 0b00100, # D.04-
72 | 0b101100: 0b01100, # D.12
73 | 0b101101: 0b00010, # D.02-
74 | 0b101110: 0b00001, # D.01-
75 | 0b101111: None, # Illegal code
76 | 0b110000: None, # Illegal code (not used)
77 | 0b110001: 0b10001, # D.17
78 | 0b110010: 0b10010, # D.18
79 | 0b110011: 0b11000, # D.24-
80 | 0b110100: 0b10100, # D.20
81 | 0b110101: 0b11111, # D.31-
82 | 0b110110: 0b10000, # D.16-
83 | 0b110111: None, # Illegal code
84 | 0b111000: 0b00111, # D.07+
85 | 0b111001: 0b00000, # D.00-
86 | 0b111010: 0b01111, # D.15-
87 | 0b111011: None, # Illegal code
88 | 0b111100: 0b11100, # K.28-
89 | 0b111101: None, # Illegal code
90 | 0b111110: None, # Illegal code
91 | 0b111111: None, # Illegal code
92 | }
93 |
94 | o_5b = lut_6b5b[i_6b] # lookup 5b/6b value
95 | o_disp = 0 # initialize output disparity
96 | o_code_err = False # initialize code error flag
97 | o_disp_err = False # initialize disparity error flag
98 |
99 | if o_5b is None: # check if illegal code
100 | o_code_err = True
101 | o_5b = 0
102 |
103 | # calculate the output disparity
104 | string_6b = format(i_6b, "06b")
105 | num_ones = string_6b.count("1")
106 | num_zeros = string_6b.count("0")
107 | codeword_disp = num_ones - num_zeros
108 |
109 | if codeword_disp != 0:
110 |
111 | # regardless of whether there is a disparity error or not
112 | # the output disparity should always get flipped if it's
113 | # not equal to zero
114 | o_disp = -i_disp
115 |
116 | # Check for disparity error
117 | if (
118 | codeword_disp >= 3
119 | or codeword_disp <= -3
120 | or (i_disp == -1 and codeword_disp == -2)
121 | or (i_disp == 1 and codeword_disp == 2)
122 | ):
123 | o_disp_err = True
124 |
125 | else: # if codeword disparity is 0 then output disparity is the input disparity
126 | o_disp = i_disp
127 |
128 | return o_5b, o_disp, o_code_err, o_disp_err
129 |
130 |
131 | def decode_3b4b(i_4b: int, i_disp: int, i_ctrl: bool) -> Tuple[int, int]:
132 | """3b/4b decoder.
133 |
134 | Args:
135 | i_4b (int): input 4b value
136 | i_disp (int): input disparity (-1 or 1)
137 | i_ctrl (bool): input control symbol flag
138 |
139 | Returns:
140 | tuple[int, int]: tuple of output 3b value and output disparity
141 | """
142 |
143 | # lookup table maps jhgf to HGF
144 | lut_4b3b_data = {
145 | # jhgf: HGF
146 | 0b0000: None, # Illegal code
147 | 0b0001: 0b111, # D.x.A7+
148 | 0b0010: 0b000, # D.x.0+
149 | 0b0011: 0b011, # D.x.3-
150 | 0b0100: 0b100, # D.x.4+
151 | 0b0101: 0b101, # D.x.5
152 | 0b0110: 0b110, # D.x.6
153 | 0b0111: 0b111, # D.x.P7-
154 | 0b1000: 0b111, # D.x.P7+
155 | 0b1001: 0b001, # D.x.1
156 | 0b1010: 0b010, # D.x.2
157 | 0b1011: 0b100, # D.x.4-
158 | 0b1100: 0b011, # D.x.3+
159 | 0b1101: 0b000, # D.x.0-
160 | 0b1110: 0b111, # D.x.A7-
161 | 0b1111: None, # Illegal code
162 | }
163 |
164 | lut_4b3b_control = {
165 | # jhgf: HGF
166 | 0b0000: None, # Illegal code
167 | 0b0001: 0b111, # K.x.7+
168 | 0b0010: 0b000, # K.x.0+
169 | 0b0011: 0b011, # K.x.3-
170 | 0b0100: 0b100, # K.x.4+
171 | 0b0101: -1, # K.x.5+ / K.x.2- (must be handled using input disparity)
172 | 0b0110: -1, # K.x.6+ / K.x.1- (must be handled using input disparity)
173 | 0b0111: None, # Illegal code
174 | 0b1000: None, # Illegal code
175 | 0b1001: -1, # K.x.6- / K.x.1+ (must be handled using input disparity)
176 | 0b1010: -1, # K.x.5- / K.x.2+ (must be handled using input disparity)
177 | 0b1011: 0b100, # K.x.4-
178 | 0b1100: 0b011, # K.x.3+
179 | 0b1101: 0b000, # K.x.0-
180 | 0b1110: 0b111, # K.x.7-
181 | 0b1111: None, # Illegal code
182 | }
183 |
184 | # lookup 3b/4b value
185 | o_3b = 0
186 | if i_ctrl:
187 | o_3b = lut_4b3b_control[i_4b]
188 |
189 | if i_4b == 0b0101: # check weird case #1
190 | if i_disp == -1:
191 | o_3b = 0b010 # K.x.2-
192 | else:
193 | o_3b = 0b101 # K.x.5+
194 | elif i_4b == 0b0110: # check weird case #2
195 | if i_disp == -1:
196 | o_3b = 0b001 # K.x.1-
197 | else: # K.x.6+
198 | o_3b = 0b110
199 | elif i_4b == 0b1001: # check weird case #3
200 | if i_disp == -1:
201 | o_3b = 0b110 # K.x.6-
202 | else:
203 | o_3b = 0b001 # K.x.1+
204 | elif i_4b == 0b1010: # check weird case #4
205 | if i_disp == -1:
206 | o_3b = 0b101 # K.x.5-
207 | else:
208 | o_3b = 0b010 # K.x.2+
209 | else:
210 | o_3b = lut_4b3b_data[i_4b]
211 |
212 | o_disp = 0 # initialize output disparity
213 | o_code_err = False # initialize code error flag
214 | o_disp_err = False # initialize disparity error flag
215 |
216 | if o_3b is None: # check if illegal code
217 | o_code_err = True
218 | o_3b = 0
219 |
220 | # calculate the output disparity
221 | string_4b = format(i_4b, "04b")
222 | num_ones = string_4b.count("1")
223 | num_zeros = string_4b.count("0")
224 | codeword_disp = num_ones - num_zeros
225 |
226 | if codeword_disp != 0:
227 |
228 | # regardless of whether there is a disparity error or not
229 | # the output disparity should always get flipped if it's
230 | # not equal to zero
231 | o_disp = -i_disp
232 |
233 | # Check for disparity error
234 | if (
235 | codeword_disp >= 3
236 | or codeword_disp <= -3
237 | or (i_disp == -1 and codeword_disp == -2)
238 | or (i_disp == 1 and codeword_disp == 2)
239 | ):
240 | o_disp_err = True
241 |
242 | else: # if codeword disparity is 0 then output disparity is the input disparity
243 | o_disp = i_disp
244 |
245 | return o_3b, o_disp, o_code_err, o_disp_err
246 |
247 |
248 | def decode_8b10b(i_10b, i_disp):
249 |
250 | # Determine if control symbol
251 | control_symbols_rdm1 = [
252 | # jhgfiedcba
253 | 0b0010111100, # K.28.0-
254 | 0b1001111100, # K.28.1-
255 | 0b1010111100, # K.28.2-
256 | 0b1100111100, # K.28.3-
257 | 0b0100111100, # K.28.4-
258 | 0b0101111100, # K.28.5-
259 | 0b0110111100, # K.28.6-
260 | 0b0001111100, # K.28.7-
261 | 0b0001010111, # K.23.7-
262 | 0b0001011011, # K.27.7-
263 | 0b0001011101, # K.29.7-
264 | 0b0001011110, # K.30.7-
265 | ]
266 | control_symbols = control_symbols_rdm1 + [
267 | (~s) & 0x3FF for s in control_symbols_rdm1
268 | ]
269 | is_ctrl = i_10b in control_symbols
270 |
271 | # Parse out 6b and 4b inputs
272 | i_6b = i_10b & 0x3F
273 | i_4b = (i_10b >> 6) & 0xF
274 |
275 | # Decode 6b
276 | o_5b, o_disp_5, o_code_err_5, o_disp_err_5 = decode_5b6b(i_6b, i_disp)
277 |
278 | # Decode 4b
279 | o_3b, o_disp_3, o_code_err_3, o_disp_err_3 = decode_3b4b(i_4b, o_disp_5, is_ctrl)
280 |
281 | o_8b = (o_3b << 5) | o_5b
282 | o_disp = o_disp_3
283 | o_ctrl = is_ctrl
284 | o_code_err = o_code_err_3 | o_code_err_5
285 | o_disp_err = o_disp_err_3 | o_disp_err_5
286 |
287 | return o_8b, o_disp, o_ctrl, o_code_err, o_disp_err
288 |
289 |
290 | @cocotb.test()
291 | async def cocotb_test_decoder_8b10b(dut):
292 | """8B/10B Decoder test"""
293 |
294 | # Create clock
295 | cocotb.start_soon(Clock(dut.i_clk, 2).start())
296 |
297 | # Assert reset and check output and
298 | # internal running disparity
299 | dut.i_reset_n.value = 0
300 | await FallingEdge(dut.i_clk)
301 | assert int(dut.o_8b) == 0
302 | assert int(dut.o_ctrl) == 0
303 | assert int(dut.o_code_err) == 0
304 | assert int(dut.o_disp_err) == 0
305 | assert int(dut.rd) == 0
306 |
307 | # Check that enable doesn't change outputs when disabled
308 | dut.i_reset_n.value = 1 # de-assert reset
309 | dut.i_en.value = 1 # enable 8b10b decoder
310 | dut.i_10b.value = 0b1001001101 # D.13.1
311 | await FallingEdge(dut.i_clk) # 1 clock tick
312 | prev_o_8b = int(dut.o_8b) # capture 8b output
313 | prev_o_ctrl = int(dut.o_ctrl) # capture control symbol flag
314 | prev_o_code_err = int(dut.o_code_err) # capture code error
315 | prev_o_disp_err = int(dut.o_disp_err) # capture disparity error
316 | prev_rd = int(dut.rd) # capture running disparity
317 | dut.i_en.value = 0 # disable 8b10b decoder
318 |
319 | # Pick a value that after the first 10b value would change both
320 | # the output 8b value, internal running disparity, and generate
321 | # both a code error and disparity error
322 | dut.i_10b.value = 0b0000000111 # Illegal code
323 |
324 | await FallingEdge(dut.i_clk) # 1 clock tick
325 | assert prev_o_8b == int(dut.o_8b) # output should not have changed
326 | assert prev_o_ctrl == int(dut.o_ctrl) # control flag should not have changed
327 | assert prev_o_code_err == int(dut.o_code_err) # code error should not have changed
328 | assert prev_o_disp_err == int(dut.o_disp_err) # disp error should not have changed
329 | assert prev_rd == int(dut.rd) # running disparity should not have changed
330 |
331 | # Test 8b/10b decoding look-up table
332 | dut.i_reset_n.value = 1
333 | dut.i_en.value = 1
334 | for i in range(2**11):
335 |
336 | # Parse out input values
337 | i_10b = i & 0x3FF
338 | i_disp = (i >> 10) & 1
339 |
340 | # Run the software 8b10b encoder
341 | py_i_10b = i_10b
342 | py_i_disp = (i_disp * 2) - 1
343 | py_o_8b, py_o_disp, py_o_ctrl, py_o_code_err, py_o_disp_err = decode_8b10b(
344 | py_i_10b, py_i_disp
345 | )
346 |
347 | # Calculate expected dut outputs using software
348 | # 8b/10b decoder results
349 | o_8b = py_o_8b
350 | o_ctrl = int(py_o_ctrl)
351 | o_code_err = int(py_o_code_err)
352 | o_disp_err = int(py_o_disp_err)
353 |
354 | # Push input to dut
355 | dut.i_10b.value = i_10b
356 |
357 | # Force internal running disparity to specific value
358 | dut.rd.value = i_disp
359 |
360 | d_prev_rd = int(dut.rd) # save previous RD value
361 |
362 | # Step 1 clock tick
363 | await FallingEdge(dut.i_clk) # 1 clock tick
364 |
365 | # Check actual outputs vs expected outputs
366 | try:
367 | assert int(dut.o_8b) == o_8b
368 | assert int(dut.o_ctrl) == o_ctrl
369 | assert int(dut.o_code_err) == o_code_err
370 | assert int(dut.o_disp_err) == o_disp_err
371 | except AssertionError as e:
372 | i_10b_s = format(i_10b, "#012b")
373 | d_i_10b_s = "0b" + str(dut.i_10b.value)
374 | d_prev_rd_s = format(d_prev_rd, "#03b")
375 | d_rd_s = "0b" + str(dut.rd.value)
376 | d_o_8b_s = "0b" + str(dut.o_8b.value)
377 | d_o_ctrl_s = "0b" + str(dut.o_ctrl.value)
378 | d_o_code_err_s = "0b" + str(dut.o_code_err.value)
379 | d_o_disp_err_s = "0b" + str(dut.o_disp_err.value)
380 | d_o_code_err_s = "0b" + str(dut.o_code_err.value)
381 | o_8b_s = format(o_8b, "#010b")
382 | o_ctrl_s = format(o_ctrl, "#03b")
383 | o_code_err_s = format(o_code_err, "#03b")
384 | o_disp_err_s = format(o_disp_err, "#03b")
385 |
386 | print(
387 | f"Error: Actual outputs do not match expected outputs\n"
388 | f" i_10b = {i_10b_s}\n"
389 | f" dut.i_10b = {d_i_10b_s}\n"
390 | f" dut.o_8b = {d_o_8b_s}, dut.o_ctrl = {d_o_ctrl_s}, "
391 | f"dut.o_code_err = {d_o_code_err_s}, "
392 | f"dut.o_disp_err = {d_o_disp_err_s}\n"
393 | f" previous dut.rd = {d_prev_rd_s}, current dut.rd = {d_rd_s}\n"
394 | f" o_8b = {o_8b_s}, o_ctrl = {o_ctrl_s}, "
395 | f"o_code_err = {o_code_err_s}, "
396 | f"o_disp_err = {o_disp_err_s}\n"
397 | )
398 | raise e
399 |
--------------------------------------------------------------------------------