From f3559036ebfb2e26dfb6ca95aea76d610c6ab51b Mon Sep 17 00:00:00 2001 From: Martin Schoeberl Date: Wed, 9 Oct 2024 22:01:39 -0700 Subject: [PATCH] Verilog and VHDL combinational blocks --- chisel-book.tex | 60 +++++++++++++++--- src/main/scala/verilog/basic.scala | 80 ++++++++++++++++++++--- src/main/vhdl/Makefile | 2 + src/main/vhdl/basic.vhdl | 87 ++++++++++++++++++++++++++ src/test/scala/verilog/BasicTest.scala | 24 ++++++- 5 files changed, 235 insertions(+), 18 deletions(-) diff --git a/chisel-book.tex b/chisel-book.tex index defe48d..7a07858 100644 --- a/chisel-book.tex +++ b/chisel-book.tex @@ -7309,17 +7309,18 @@ \subsection{Components} a component is also called a module.~\footnote{In fact, the naming in Chisel was inspired by Verilog.} The adder has two 8-bit inputs and one 8-bit output. - +The \code{assign} statement is a concurrent statement that continuously +assigns the value of the sum of \code{a} and \code{b} to the output port \code{sum}. The difference between Verilog and Chisel is minimal for such a simple component. \subsection{Using a Component} -\longlist{code/v_use_chisel_adder.txt}{Using the \code{Adder} component in Chisel.}{lst:ch:use} +\longlist{code/v_use_chisel_adder.txt}{Using the \code{ChiselAdder} component in Chisel.}{lst:ch:use} \longlist{code/vhdl_use_adder.txt}{Using the \code{adder} component in VHDL.}{lst:vhdl:use} -\longlist{code/v_use_adder.txt}{Using the \code{Adder} component in Verilog.}{lst:v:use} +\longlist{code/v_use_adder.txt}{Using the \code{adder} component in Verilog.}{lst:v:use} Listing~\ref{lst:ch:use} shows how to create a component with \code{new} in Chisel, @@ -7339,7 +7340,7 @@ \subsection{Using a Component} \code{in1}, \code{in2}, and \code{result} are the wires/signals connected to those ports. The instances of the adders in all three examples where named \code{m}. In VHDL and Verilog, the name of the component is not used so often, in Chisel we need -the name to access the IO ports. +the name of the module to access the IO ports. \subsection{Register} @@ -7368,24 +7369,63 @@ \subsection{Register} register (\code{reg\_data}) with a synchronous reset and an enable input in Verilog. The Verilog \code{reg} keyword declares a variable. This does not necessarily mean that the variable represents a register. It can also represent combinational logic -when used in an \code{always\_comb} block. -The \code{always_ff \@} statement with a clock implies that the code within that block +when used in an \code{always @(*)} block. +The \code{always @(posedge clk)} statement with a clock implies that the code within that block represents a register. -%The \code{assign} statement is a concurrent statement that continuously -%assigns the value of the register to the output port \code{out}. Registers are defined implicitly by a code pattern in Verilog and VHDL. Whereas, in Chisel the registers are defined explicitly. +\subsection{Combinational Blocks} + +Simple combinational logic can be written by an assignment as a concurrent +statement, in all three languages. We have seen examples in the adder components. +However, for more complex logic, e.g. describing nested conditions, it is more convenient +to use blocks for combinational logic. In Verilog this is an \code{always} block, +in VHDL this is a \code{process}. + + +\longlist{code/v_ch_comb.txt}{A \code{switch} statement in Chisel.}{lst:v:ch:comb} + +Listing~\ref{lst:v:ch:comb} shows as an example of a combinational block a \code{switch} +statement in Chisel. This code is a 4:1 multiplexer. In Chisel the default value +for the output needs to be assigned before the \code{switch} statement. + +\longlist{code/vhdl_case.txt}{A \code{case} statement in VHDL.}{lst:vhdl:comb} + +Listing~\ref{lst:vhdl:comb} shows the same combinational block in VHDL. +A combinational block is a \code{process}. Start start is additionally marked with +a \code{begin} and the end of the process with \code{end process;} +The default value for that \code{case} +statement is assigned in with a \code{when others} entry. +Note that a VHDL process has a sensitive list, where all signals that appear +on the righthand side of an expression or a a condition need to be listed. +In our example this is \code{sel} and \code{input}. + +\longlist{code/v_comb.txt}{A \code{case} statement in Verilog.}{lst:v:comb} + +Listing~\ref{lst:v:comb} shows the same combinational block in Verilog. +Note the marking of the start of combinational block with \code{always @(*) begin} +and the end of the block with \code{end}. The default value for that \code{case} +statement is assigned in with a \code{default} entry. +This example includes to module header to show that the output \code{out} +needs to be declared as \code{reg}, as is is assigned in an \code{always} block. + +Note that Verilog has two different assignment operators: the \code{=} is called +a \emph{blocking} statement and the \code{<=} a \emph{non-blocking} statement. +This might also be a source of confusion. Use the \code{<=} assignment operator +for registers (in an \code{always @(posedge clk)} block) and the \code{=} +assignment operator +for combinational logic (in an \code{always @(*)} block) + \subsection{Advanced Chisel Features} The basic elements look similar in Chisel, Verilog, and VHDL, and the verbosity is also in the same range (although VHDL is a bit chattery). However, neither VHDL nor Verilog contain object-oriented features for hardware -description. Verilog includes object-oriented programming only for writing test benches. +description. SystemVerilog includes object-oriented programming only for writing test benches. Both languages are missing functional programing, which is important to write hardware generators. - Furthermore, \code{Bundle}s with directions and the possibility to flip the direction are also missing. diff --git a/src/main/scala/verilog/basic.scala b/src/main/scala/verilog/basic.scala index 431ced3..344fa6f 100644 --- a/src/main/scala/verilog/basic.scala +++ b/src/main/scala/verilog/basic.scala @@ -24,7 +24,7 @@ class UseChiselAdder extends Module { val in2 = Wire(UInt(8.W)) val result = Wire(UInt(8.W)) - val m = Module(new Adder) + val m = Module(new ChiselAdder) m.io.a := in1 m.io.b := in2 result := m.io.sum @@ -34,16 +34,16 @@ class UseChiselAdder extends Module { in2 := io.b io.sum := result } -class Adder extends BlackBox with HasBlackBoxInline { +class adder extends BlackBox with HasBlackBoxInline { val io = IO(new Bundle() { val a, b = Input(UInt(8.W)) val sum = Output(UInt(8.W)) }) - setInline("Adder.v", + setInline("adder.v", """ //- start v_adder -module Adder( +module adder( input [7:0] a, input [7:0] b, output [7:0] sum @@ -65,7 +65,7 @@ class UseAdder extends BlackBox with HasBlackBoxInline { setInline("UseAdder.v", """ //- start v_adder -module Adder( +module adder( input [7:0] a, input [7:0] b, output [7:0] sum @@ -79,7 +79,7 @@ endmodule module UseAdder( input [7:0] a, input [7:0] b, - output reg [7:0] sum + output [7:0] sum ); //- start v_use_adder @@ -87,7 +87,7 @@ module UseAdder( wire [7:0] in2; wire [7:0] result; - Adder m(.a(in1), .b(in2), .sum(result)); + adder m(.a(in1), .b(in2), .sum(result)); //- end assign in1 = a; @@ -183,3 +183,69 @@ class RegisterTop extends Module { cm.io.data := io.in io.out2 := cm.io.out } + +class comb extends BlackBox with HasBlackBoxInline { + val io = IO(new Bundle() { + val sel = Input(UInt(2.W)) + val in = Input(UInt(4.W)) + val out = Output(UInt(1.W)) + }) + + setInline("comb.v", + """ +//- start v_comb +module comb( + input [1:0] sel, + input [3:0] in, + output reg out +); + + always @(*) begin + case (sel) + 2'b00: out = in[0]; + 2'b01: out = in[1]; + 2'b10: out = in[2]; + 2'b11: out = in[3]; + default: out = 1'b0; + endcase + end + +endmodule +//- end +""")} + +class ChiselComb extends Module { + val io = IO(new Bundle() { + val sel = Input(UInt(2.W)) + val in = Input(UInt(4.W)) + val out = Output(UInt(1.W)) + }) + //- start v_ch_comb + io.out := 0.U + switch(io.sel) { + is("b00".U) { io.out := io.in(0) } + is("b01".U) { io.out := io.in(1) } + is("b10".U) { io.out := io.in(2) } + is("b11".U) { io.out := io.in(3) } + } + //- end +} + +class CombTop extends Module { + val io = IO(new Bundle() { + val sel = Input(UInt(2.W)) + val in = Input(UInt(4.W)) + val out = Output(UInt(1.W)) + val out2 = Output(UInt(1.W)) + }) + + val m = Module(new comb) + m.io.sel := io.sel + m.io.in := io.in + io.out := m.io.out + val cm = Module(new ChiselComb) + cm.io.sel := io.sel + cm.io.in := io.in + io.out2 := cm.io.out +} + diff --git a/src/main/vhdl/Makefile b/src/main/vhdl/Makefile index 4867456..a10d93f 100644 --- a/src/main/vhdl/Makefile +++ b/src/main/vhdl/Makefile @@ -5,3 +5,5 @@ all: ghdl -a basic.vhdl ghdl -e adder_tb ghdl -r adder_tb + ghdl -e Mux4to1_tb + ghdl -r Mux4to1_tb diff --git a/src/main/vhdl/basic.vhdl b/src/main/vhdl/basic.vhdl index 546c353..fa1909e 100644 --- a/src/main/vhdl/basic.vhdl +++ b/src/main/vhdl/basic.vhdl @@ -182,4 +182,91 @@ begin report "Simulation finished" severity note; wait; end process; +end architecture; + + +library ieee; +use ieee.std_logic_1164.all; + +entity Mux4to1 is + port ( + sel : in std_logic_vector(1 downto 0); + input : in std_logic_vector(3 downto 0); + output : out std_logic + ); +end entity; + +architecture rtl of Mux4to1 is +begin +--/ start vhdl_case + process (sel, input) + begin + case sel is + when "00" => output <= input(0); + when "01" => output <= input(1); + when "10" => output <= input(2); + when "11" => output <= input(3); + when others => output <= '0'; + end case; + end process; +--/ end +end architecture; + +library ieee; +use ieee.std_logic_1164.all; + +entity Mux4to1_tb is +end entity; + +architecture behavioral of Mux4to1_tb is + signal sel_tb : std_logic_vector(1 downto 0) := "00"; + signal in_tb : std_logic_vector(3 downto 0) := "0000"; + signal out_tb : std_logic; + + component Mux4to1 + port ( + sel : in std_logic_vector(1 downto 0); + input : in std_logic_vector(3 downto 0); + output : out std_logic + ); + end component; + +begin + uut: Mux4to1 + port map ( + sel => sel_tb, + input => in_tb, + output => out_tb + ); + + process + begin + -- Test case 1 + in_tb <= "0001"; sel_tb <= "00"; + wait for 10 ns; + assert (out_tb = '1') report "Test case 1 failed" severity error; + + -- Test case 2 + in_tb <= "0010"; sel_tb <= "01"; + wait for 10 ns; + assert (out_tb = '1') report "Test case 2 failed" severity error; + + -- Test case 3 + in_tb <= "0100"; sel_tb <= "10"; + wait for 10 ns; + assert (out_tb = '1') report "Test case 3 failed" severity error; + + -- Test case 4 + in_tb <= "1000"; sel_tb <= "11"; + wait for 10 ns; + assert (out_tb = '1') report "Test case 4 failed" severity error; + + -- Test case 5 + in_tb <= "0000"; sel_tb <= "00"; + wait for 10 ns; + assert (out_tb = '0') report "Test case 5 failed" severity error; + + report "All test cases passed" severity note; + wait; + end process; end architecture; \ No newline at end of file diff --git a/src/test/scala/verilog/BasicTest.scala b/src/test/scala/verilog/BasicTest.scala index ebb2cae..1065b7b 100644 --- a/src/test/scala/verilog/BasicTest.scala +++ b/src/test/scala/verilog/BasicTest.scala @@ -17,7 +17,7 @@ class BasicTest extends AnyFlatSpec with ChiselScalatestTester { dut.io.s.expect(4.U) } } - +/* "Use Adder" should "pass" in { test(new AdderTop()).withAnnotations(Seq(VerilatorBackendAnnotation)) { dut => dut.io.a.poke(1.U) @@ -31,6 +31,8 @@ class BasicTest extends AnyFlatSpec with ChiselScalatestTester { } } + */ + "Register" should "pass" in { test(new RegisterTop()).withAnnotations(Seq(VerilatorBackendAnnotation)) { dut => dut.io.en.poke(1.U) @@ -55,4 +57,24 @@ class BasicTest extends AnyFlatSpec with ChiselScalatestTester { dut.io.out2.expect(123.U) } } + "Comb" should "pass" in { + test(new CombTop()).withAnnotations(Seq(VerilatorBackendAnnotation)) { dut => + dut.io.in.poke(1.U) + dut.io.sel.poke(0.U) + dut.clock.step() + dut.io.out.expect(1.U) + dut.io.sel.poke(1.U) + dut.clock.step() + dut.io.out.expect(0.U) + dut.io.in.poke(2.U) + dut.io.sel.poke(0.U) + dut.clock.step() + dut.io.out.expect(0.U) + dut.io.sel.poke(1.U) + dut.clock.step() + dut.io.out.expect(1.U) + + } + + } }