Skip to content

Commit

Permalink
Verilog and VHDL combinational blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
schoeberl committed Oct 10, 2024
1 parent 93a8a33 commit f355903
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 18 deletions.
60 changes: 50 additions & 10 deletions chisel-book.tex
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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}
Expand Down Expand Up @@ -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.
Expand Down
80 changes: 73 additions & 7 deletions src/main/scala/verilog/basic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -79,15 +79,15 @@ endmodule
module UseAdder(
input [7:0] a,
input [7:0] b,
output reg [7:0] sum
output [7:0] sum
);
//- start v_use_adder
wire [7:0] in1;
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;
Expand Down Expand Up @@ -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
}

2 changes: 2 additions & 0 deletions src/main/vhdl/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
87 changes: 87 additions & 0 deletions src/main/vhdl/basic.vhdl
Original file line number Diff line number Diff line change
Expand Up @@ -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;
24 changes: 23 additions & 1 deletion src/test/scala/verilog/BasicTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)

}

}
}

0 comments on commit f355903

Please sign in to comment.